From be5cf6b5fd9f5174e4ea16ca8f1ee96c2c746f2d Mon Sep 17 00:00:00 2001 From: Colin Kho Date: Thu, 2 May 2024 22:55:31 -0700 Subject: [PATCH] Allow LoadControl.shouldContinueLoading accept playWhenReady as a parameter --- .../media3/exoplayer/DefaultLoadControl.java | 21 +-- .../exoplayer/ExoPlayerImplInternal.java | 25 +-- .../media3/exoplayer/LoadControl.java | 31 ++-- .../media3/exoplayer/LoadParameters.java | 60 ++++++ .../exoplayer/DefaultLoadControlTest.java | 172 ++++++++++++------ .../media3/exoplayer/ExoPlayerTest.java | 8 +- 6 files changed, 212 insertions(+), 105 deletions(-) create mode 100644 libraries/exoplayer/src/main/java/androidx/media3/exoplayer/LoadParameters.java diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultLoadControl.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultLoadControl.java index 4861312f88..682267355d 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultLoadControl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultLoadControl.java @@ -379,34 +379,29 @@ public class DefaultLoadControl implements LoadControl { } @Override - public boolean shouldContinueLoading( - PlayerId playerId, - Timeline timeline, - MediaPeriodId mediaPeriodId, - long playbackPositionUs, - long bufferedDurationUs, - float playbackSpeed) { - PlayerLoadingState playerLoadingState = checkNotNull(loadingStates.get(playerId)); + public boolean shouldContinueLoading(LoadParameters loadParameters) { + PlayerLoadingState playerLoadingState = + checkNotNull(loadingStates.get(loadParameters.playerId)); boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= calculateTotalTargetBufferBytes(); long minBufferUs = this.minBufferUs; - if (playbackSpeed > 1) { + if (loadParameters.playbackSpeed > 1) { // The playback speed is faster than real time, so scale up the minimum required media // duration to keep enough media buffered for a playout duration of minBufferUs. long mediaDurationMinBufferUs = - Util.getMediaDurationForPlayoutDuration(minBufferUs, playbackSpeed); + Util.getMediaDurationForPlayoutDuration(minBufferUs, loadParameters.playbackSpeed); minBufferUs = min(mediaDurationMinBufferUs, maxBufferUs); } // Prevent playback from getting stuck if minBufferUs is too small. minBufferUs = max(minBufferUs, 500_000); - if (bufferedDurationUs < minBufferUs) { + if (loadParameters.bufferedDurationUs < minBufferUs) { playerLoadingState.isLoading = prioritizeTimeOverSizeThresholds || !targetBufferSizeReached; - if (!playerLoadingState.isLoading && bufferedDurationUs < 500_000) { + if (!playerLoadingState.isLoading && loadParameters.bufferedDurationUs < 500_000) { Log.w( "DefaultLoadControl", "Target buffer size reached with less than 500ms of buffered media data."); } - } else if (bufferedDurationUs >= maxBufferUs || targetBufferSizeReached) { + } else if (loadParameters.bufferedDurationUs >= maxBufferUs || targetBufferSizeReached) { playerLoadingState.isLoading = false; } // Else don't change the loading state. return playerLoadingState.isLoading; diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java index 2c099aba80..3bb94e5508 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java @@ -2577,14 +2577,15 @@ import java.util.concurrent.atomic.AtomicBoolean; ? loadingPeriodHolder.toPeriodTime(rendererPositionUs) : loadingPeriodHolder.toPeriodTime(rendererPositionUs) - loadingPeriodHolder.info.startPositionUs; - boolean shouldContinueLoading = - loadControl.shouldContinueLoading( - playerId, - playbackInfo.timeline, - loadingPeriodHolder.info.id, - playbackPositionUs, - bufferedDurationUs, - mediaClock.getPlaybackParameters().speed); + LoadParameters loadParameters = new LoadParameters( + playerId, + playbackInfo.timeline, + loadingPeriodHolder.info.id, + playbackPositionUs, + bufferedDurationUs, + mediaClock.getPlaybackParameters().speed, + playbackInfo.playWhenReady); + boolean shouldContinueLoading = loadControl.shouldContinueLoading(loadParameters); MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); if (!shouldContinueLoading && playingPeriodHolder.prepared @@ -2595,13 +2596,7 @@ import java.util.concurrent.atomic.AtomicBoolean; playingPeriodHolder.mediaPeriod.discardBuffer( playbackInfo.positionUs, /* toKeyframe= */ false); shouldContinueLoading = - loadControl.shouldContinueLoading( - playerId, - playbackInfo.timeline, - loadingPeriodHolder.info.id, - playbackPositionUs, - bufferedDurationUs, - mediaClock.getPlaybackParameters().speed); + loadControl.shouldContinueLoading(loadParameters); } return shouldContinueLoading; } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/LoadControl.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/LoadControl.java index 88a66a68ac..378fef914b 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/LoadControl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/LoadControl.java @@ -220,20 +220,26 @@ public interface LoadControl { * returns true, the {@link MediaPeriod} identified in the most recent {@link #onTracksSelected} * call will continue being loaded. * - * @param playerId The {@linkplain PlayerId ID of the player} that wants to continue loading. - * @param timeline The current {@link Timeline} in ExoPlayer. - * @param mediaPeriodId Identifies (in the current timeline) the {@link MediaPeriod} that is - * currently loading. - * @param playbackPositionUs The current playback position in microseconds, relative to the start - * of the {@link Timeline.Period period} that will continue to be loaded if this method - * returns {@code true}. If playback of this period has not yet started, the value will be - * negative and equal in magnitude to the duration of any media in previous periods still to - * be played. - * @param bufferedDurationUs The duration of media that's currently buffered. - * @param playbackSpeed The current factor by which playback is sped up. + * @param loadParameters Parameters for Load Control. Refer to {@link LoadParameters} for more + * information on the individual parameters * @return Whether the loading should continue. */ + @SuppressWarnings("deprecation") + default boolean shouldContinueLoading(final LoadParameters loadParameters) { + return shouldContinueLoading( + loadParameters.playerId, + loadParameters.timeline, + loadParameters.mediaPeriodId, + loadParameters.playbackPositionUs, + loadParameters.bufferedDurationUs, + loadParameters.playbackSpeed); + } + + /** + * @deprecated Implement {@link #shouldContinueLoading(LoadParameters)} instead. + */ @SuppressWarnings("deprecation") // Calling deprecated version of this method. + @Deprecated default boolean shouldContinueLoading( PlayerId playerId, Timeline timeline, @@ -245,8 +251,7 @@ public interface LoadControl { } /** - * @deprecated Implement {@link #shouldContinueLoading(PlayerId, Timeline, MediaPeriodId, long, - * long, float)} instead. + * @deprecated Implement {@link #shouldContinueLoading(LoadParameters)} instead. */ @Deprecated default boolean shouldContinueLoading( diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/LoadParameters.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/LoadParameters.java new file mode 100644 index 0000000000..1c7070a6de --- /dev/null +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/LoadParameters.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016 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 androidx.media3.exoplayer; + +import androidx.media3.common.Player; +import androidx.media3.common.Timeline; +import androidx.media3.exoplayer.analytics.PlayerId; +import androidx.media3.exoplayer.source.MediaPeriod; +import androidx.media3.exoplayer.source.MediaSource; + +public class LoadParameters { + /* Identifier for a player instance. */ + public final PlayerId playerId; + /* A flexible representation of the structure of media. */ + public final Timeline timeline; + /* Identifier for a {@link MediaPeriod}. */ + public final MediaSource.MediaPeriodId mediaPeriodId; + /* The current playback position in microseconds, relative to the start + * of the {@link Timeline.Period period} that will continue to be loaded if this method + * returns {@code true}. If playback of this period has not yet started, the value will be + * negative and equal in magnitude to the duration of any media in previous periods still to + * be played. + */ + public final long playbackPositionUs; + /* The duration of media that's currently buffered. */ + public final long bufferedDurationUs; + /* The current factor by which playback is sped up. */ + public final float playbackSpeed; + /** Whether playback should proceed when {@link Player#STATE_READY}. */ + public final boolean playWhenReady; + + public LoadParameters(final PlayerId playerId, + final Timeline timeline, + final MediaSource.MediaPeriodId mediaPeriodId, + final long playbackPositionUs, + final long bufferedDurationUs, + final float playbackSpeed, + final boolean playWhenReady) { + this.playerId = playerId; + this.timeline = timeline; + this.mediaPeriodId = mediaPeriodId; + this.playbackPositionUs = playbackPositionUs; + this.bufferedDurationUs = bufferedDurationUs; + this.playbackSpeed = playbackSpeed; + this.playWhenReady = playWhenReady; + } +} diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/DefaultLoadControlTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/DefaultLoadControlTest.java index 0db4cc446e..afe07beae0 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/DefaultLoadControlTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/DefaultLoadControlTest.java @@ -80,31 +80,34 @@ public class DefaultLoadControlTest { public void shouldContinueLoading_untilMaxBufferExceeded() { build(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, /* bufferedDurationUs= */ 0L, - SPEED)) + SPEED, + false))) .isTrue(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MAX_BUFFER_US - 1, - SPEED)) + SPEED, + false))) .isTrue(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MAX_BUFFER_US, - SPEED)) + SPEED, + false))) .isFalse(); } @@ -125,51 +128,59 @@ public class DefaultLoadControlTest { loadControl.onPrepared(playerId2); // First player is fully buffered. Buffer starts depleting until it falls under min size. loadControl.shouldContinueLoading( - playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MAX_BUFFER_US, SPEED); + new LoadParameters( + playerId, timeline, mediaPeriodId, + /* playbackPositionUs= */ 0L, MAX_BUFFER_US, SPEED, + false)); // Second player fell below min size and starts loading until max size is reached. - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId2, timeline2, mediaPeriodId2, /* playbackPositionUs= */ 0L, MIN_BUFFER_US - 1, - SPEED); + SPEED, + false)); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MAX_BUFFER_US - 1, - SPEED)) + SPEED, + false))) .isFalse(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId2, timeline2, mediaPeriodId2, /* playbackPositionUs= */ 0L, MIN_BUFFER_US, - SPEED)) + SPEED, + false))) .isTrue(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MIN_BUFFER_US, - SPEED)) + SPEED, + false))) .isFalse(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId2, timeline2, mediaPeriodId2, /* playbackPositionUs= */ 0L, MAX_BUFFER_US - 1, - SPEED)) + SPEED, + false))) .isTrue(); } @@ -183,40 +194,44 @@ public class DefaultLoadControlTest { build(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MAX_BUFFER_US, - SPEED)) + SPEED, + false))) .isFalse(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MAX_BUFFER_US - 1, - SPEED)) + SPEED, + false))) .isFalse(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MIN_BUFFER_US, - SPEED)) + SPEED, + false))) .isFalse(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MIN_BUFFER_US - 1, - SPEED)) + SPEED, + false))) .isTrue(); } @@ -230,26 +245,30 @@ public class DefaultLoadControlTest { build(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MAX_BUFFER_US, - SPEED)) + SPEED, + false))) .isFalse(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, 5 * C.MICROS_PER_SECOND, - SPEED)) + SPEED, + false))) .isFalse(); assertThat( - loadControl.shouldContinueLoading( - playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, 500L, SPEED)) + loadControl.shouldContinueLoading(new LoadParameters( + playerId, timeline, mediaPeriodId, + /* playbackPositionUs= */ 0L, 500L, SPEED, + false))) .isTrue(); } @@ -265,40 +284,44 @@ public class DefaultLoadControlTest { makeSureTargetBufferBytesReached(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, /* bufferedDurationUs= */ 0L, - SPEED)) + SPEED, + false))) .isTrue(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MIN_BUFFER_US - 1, - SPEED)) + SPEED, + false))) .isTrue(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MIN_BUFFER_US, - SPEED)) + SPEED, + false))) .isFalse(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MAX_BUFFER_US, - SPEED)) + SPEED, + false))) .isFalse(); } @@ -310,51 +333,56 @@ public class DefaultLoadControlTest { // Put loadControl in buffering state. assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, /* bufferedDurationUs= */ 0L, - SPEED)) + SPEED, + false))) .isTrue(); makeSureTargetBufferBytesReached(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, /* bufferedDurationUs= */ 0L, - SPEED)) + SPEED, + false))) .isFalse(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MIN_BUFFER_US - 1, - SPEED)) + SPEED, + false))) .isFalse(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MIN_BUFFER_US, - SPEED)) + SPEED, + false))) .isFalse(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MAX_BUFFER_US, - SPEED)) + SPEED, + false))) .isFalse(); } @@ -369,23 +397,25 @@ public class DefaultLoadControlTest { // At normal playback speed, we stop buffering when the buffer reaches the minimum. assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MIN_BUFFER_US, - SPEED)) + SPEED, + false))) .isFalse(); // At double playback speed, we continue loading. assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MIN_BUFFER_US, - /* playbackSpeed= */ 2f)) + /* playbackSpeed= */ 2f, + false))) .isTrue(); } @@ -402,13 +432,14 @@ public class DefaultLoadControlTest { new ExoTrackSelection[0]); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, /* bufferedDurationUs= */ 0L, - /* playbackSpeed= */ 1f)) + /* playbackSpeed= */ 1f, + false))) .isTrue(); } @@ -417,13 +448,14 @@ public class DefaultLoadControlTest { build(); assertThat( - loadControl.shouldContinueLoading( + loadControl.shouldContinueLoading(new LoadParameters( playerId, timeline, mediaPeriodId, /* playbackPositionUs= */ 0L, MAX_BUFFER_US, - /* playbackSpeed= */ 100f)) + /* playbackSpeed= */ 100f, + false))) .isFalse(); } @@ -638,6 +670,32 @@ public class DefaultLoadControlTest { assertThat(loadControl.calculateTotalTargetBufferBytes()).isEqualTo(0); } + @Test + public void shouldContinueLoading_backwardCompatible() { + final LoadControl oldLoadControl = + new DefaultLoadControl() { + @Override + public boolean shouldContinueLoading(LoadParameters loadParameters) { + return super.shouldContinueLoading(loadParameters); + } + + @Override + public boolean shouldContinueLoading( + long playbackPositionUs, long bufferedDurationUs, float playbackSpeed) { + return true; + } + }; + + oldLoadControl.onPrepared(playerId); + + final LoadParameters loadParameters = + new LoadParameters(playerId, timeline, mediaPeriodId, + 0, 0, SPEED, false); + assertThat( + oldLoadControl.shouldContinueLoading(loadParameters)) + .isTrue(); + } + private void build() { builder.setAllocator(allocator).setTargetBufferBytes(TARGET_BUFFER_BYTES); loadControl = builder.build(); diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java index aaf98fe2e0..fc04d953c1 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java @@ -6046,13 +6046,7 @@ public class ExoPlayerTest { LoadControl neverLoadingLoadControl = new DefaultLoadControl() { @Override - public boolean shouldContinueLoading( - PlayerId playerId, - Timeline timeline, - MediaPeriodId mediaPeriodid, - long playbackPositionUs, - long bufferedDurationUs, - float playbackSpeed) { + public boolean shouldContinueLoading(LoadParameters loadParameters) { return false; }