From f88280dc7866e3199d3dbd1452714d4469b16ad3 Mon Sep 17 00:00:00 2001 From: Googler Date: Tue, 28 Mar 2023 15:38:11 +0000 Subject: [PATCH] Allow associating LoadControl methods with the relevant MediaPeriod. PiperOrigin-RevId: 520037412 --- RELEASENOTES.md | 3 + .../media3/exoplayer/DefaultLoadControl.java | 15 +++- .../exoplayer/ExoPlayerImplInternal.java | 21 +++++- .../media3/exoplayer/LoadControl.java | 73 +++++++++++++++++-- .../exoplayer/DefaultLoadControlTest.java | 29 +++++++- 5 files changed, 128 insertions(+), 13 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ae2d288e4c..e2c9411e0e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -13,6 +13,9 @@ `SampleQueue.sourceId` and `SampleQueue.peekSourceId`. * Reset target live stream override when seeking to default position ([#11051](https://github.com/google/ExoPlayer/pull/11051)). + * Add parameters to `LoadControl` methods `shouldStartPlayback` and + `onTracksSelected` that allow associating these methods with the + relevant `MediaPeriod`. * Audio: * Fix bug where some playbacks fail when tunneling is enabled and `AudioProcessors` are active, e.g. for gapless trimming 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 2077c1ee33..fb9d4cf2c4 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultLoadControl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultLoadControl.java @@ -21,6 +21,8 @@ import static java.lang.Math.min; import androidx.annotation.Nullable; import androidx.media3.common.C; +import androidx.media3.common.MediaPeriodId; +import androidx.media3.common.Timeline; import androidx.media3.common.util.Assertions; import androidx.media3.common.util.Log; import androidx.media3.common.util.UnstableApi; @@ -330,7 +332,11 @@ public class DefaultLoadControl implements LoadControl { @Override public void onTracksSelected( - Renderer[] renderers, TrackGroupArray trackGroups, ExoTrackSelection[] trackSelections) { + Timeline timeline, + MediaPeriodId mediaPeriodId, + Renderer[] renderers, + TrackGroupArray trackGroups, + ExoTrackSelection[] trackSelections) { targetBufferBytes = targetBufferBytesOverwrite == C.LENGTH_UNSET ? calculateTargetBufferBytes(renderers, trackSelections) @@ -392,7 +398,12 @@ public class DefaultLoadControl implements LoadControl { @Override public boolean shouldStartPlayback( - long bufferedDurationUs, float playbackSpeed, boolean rebuffering, long targetLiveOffsetUs) { + Timeline timeline, + MediaPeriodId mediaPeriodId, + long bufferedDurationUs, + float playbackSpeed, + boolean rebuffering, + long targetLiveOffsetUs) { bufferedDurationUs = Util.getPlayoutDurationForMediaDuration(bufferedDurationUs, playbackSpeed); long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs; if (targetLiveOffsetUs != C.TIME_UNSET) { 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 c42044fef1..83915dc840 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java @@ -1823,8 +1823,9 @@ import java.util.concurrent.atomic.AtomicBoolean; return true; } // Renderers are ready and we're loading. Ask the LoadControl whether to transition. + MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); long targetLiveOffsetUs = - shouldUseLivePlaybackSpeedControl(playbackInfo.timeline, queue.getPlayingPeriod().info.id) + shouldUseLivePlaybackSpeedControl(playbackInfo.timeline, playingPeriodHolder.info.id) ? livePlaybackSpeedControl.getTargetLiveOffsetUs() : C.TIME_UNSET; MediaPeriodHolder loadingHolder = queue.getLoadingPeriod(); @@ -1836,6 +1837,8 @@ import java.util.concurrent.atomic.AtomicBoolean; return isBufferedToEnd || isAdPendingPreparation || loadControl.shouldStartPlayback( + playbackInfo.timeline, + playingPeriodHolder.info.id, getTotalBufferedDurationUs(), mediaClock.getPlaybackParameters().speed, isRebuffering, @@ -2291,7 +2294,9 @@ import java.util.concurrent.atomic.AtomicBoolean; loadingPeriodHolder.handlePrepared( mediaClock.getPlaybackParameters().speed, playbackInfo.timeline); updateLoadControlTrackSelection( - loadingPeriodHolder.getTrackGroups(), loadingPeriodHolder.getTrackSelectorResult()); + loadingPeriodHolder.info.id, + loadingPeriodHolder.getTrackGroups(), + loadingPeriodHolder.getTrackSelectorResult()); if (loadingPeriodHolder == queue.getPlayingPeriod()) { // This is the first prepared period, so update the position and the renderers. resetRendererPosition(loadingPeriodHolder.info.startPositionUs); @@ -2576,6 +2581,7 @@ import java.util.concurrent.atomic.AtomicBoolean; && loadingMediaPeriodHolder != null && loadingMediaPeriodHolder.prepared) { updateLoadControlTrackSelection( + loadingMediaPeriodHolder.info.id, loadingMediaPeriodHolder.getTrackGroups(), loadingMediaPeriodHolder.getTrackSelectorResult()); } @@ -2596,8 +2602,15 @@ import java.util.concurrent.atomic.AtomicBoolean; } private void updateLoadControlTrackSelection( - TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) { - loadControl.onTracksSelected(renderers, trackGroups, trackSelectorResult.selections); + MediaPeriodId mediaPeriodId, + TrackGroupArray trackGroups, + TrackSelectorResult trackSelectorResult) { + loadControl.onTracksSelected( + playbackInfo.timeline, + mediaPeriodId, + renderers, + trackGroups, + trackSelectorResult.selections); } private boolean shouldPlayWhenReady() { 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 8f8c37c8d4..a03e6e45e4 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/LoadControl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/LoadControl.java @@ -16,9 +16,11 @@ package androidx.media3.exoplayer; import androidx.media3.common.C; +import androidx.media3.common.MediaPeriodId; import androidx.media3.common.Timeline; import androidx.media3.common.TrackGroup; import androidx.media3.common.util.UnstableApi; +import androidx.media3.exoplayer.source.MediaPeriod; import androidx.media3.exoplayer.source.TrackGroupArray; import androidx.media3.exoplayer.trackselection.ExoTrackSelection; import androidx.media3.exoplayer.upstream.Allocator; @@ -27,18 +29,49 @@ import androidx.media3.exoplayer.upstream.Allocator; @UnstableApi public interface LoadControl { + /** + * @deprecated Used as a placeholder when MediaPeriodId is unknown. Only used when the deprecated + * methods {@link #onTracksSelected(Renderer[], TrackGroupArray, ExoTrackSelection[])} or + * {@link #shouldStartPlayback(long, float, boolean, long)} are called. + */ + @Deprecated + MediaPeriodId EMPTY_MEDIA_PERIOD_ID = new MediaPeriodId(/* periodUid= */ new Object()); + /** Called by the player when prepared with a new source. */ void onPrepared(); /** * Called by the player when a track selection occurs. * + * @param timeline The current {@link Timeline} in ExoPlayer. Can be {@link Timeline#EMPTY} only + * when the deprecated {@link #onTracksSelected(Renderer[], TrackGroupArray, + * ExoTrackSelection[])} was called. + * @param mediaPeriodId Identifies (in the current timeline) the {@link MediaPeriod} for which the + * selection was made. Will be {@link #EMPTY_MEDIA_PERIOD_ID} when {@code timeline} is empty. * @param renderers The renderers. * @param trackGroups The {@link TrackGroup}s from which the selection was made. * @param trackSelections The track selections that were made. */ - void onTracksSelected( - Renderer[] renderers, TrackGroupArray trackGroups, ExoTrackSelection[] trackSelections); + @SuppressWarnings("deprecation") // Calling deprecated version of this method. + default void onTracksSelected( + Timeline timeline, + MediaPeriodId mediaPeriodId, + Renderer[] renderers, + TrackGroupArray trackGroups, + ExoTrackSelection[] trackSelections) { + onTracksSelected(renderers, trackGroups, trackSelections); + } + + /** + * @deprecated Implement {@link #onTracksSelected(Timeline, MediaPeriodId, Renderer[], + * TrackGroupArray, ExoTrackSelection[])} instead. + */ + @Deprecated + default void onTracksSelected( + Renderer[] renderers, TrackGroupArray trackGroups, ExoTrackSelection[] trackSelections) { + onTracksSelected( + Timeline.EMPTY, EMPTY_MEDIA_PERIOD_ID, renderers, trackGroups, trackSelections); + } /** Called by the player when stopped. */ void onStopped(); @@ -84,7 +117,9 @@ public interface LoadControl { boolean retainBackBufferFromKeyframe(); /** - * 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. If this method + * returns true, the {@link MediaPeriod} identified in the most recent {@link #onTracksSelected} + * call will continue being loaded. * * @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 @@ -104,6 +139,10 @@ public interface LoadControl { * determines whether playback is actually started. The load control may opt to return {@code * false} until some condition has been met (e.g. a certain amount of media is buffered). * + * @param timeline The current {@link Timeline} in ExoPlayer. Can be {@link Timeline#EMPTY} only + * when the deprecated {@link #shouldStartPlayback(long, float, boolean, long)} was called. + * @param mediaPeriodId Identifies (in the current timeline) the {@link MediaPeriod} for which + * playback will start. Will be {@link #EMPTY_MEDIA_PERIOD_ID} when {@code timeline} is empty. * @param bufferedDurationUs The duration of media that's currently buffered. * @param playbackSpeed The current factor by which playback is sped up. * @param rebuffering Whether the player is rebuffering. A rebuffer is defined to be caused by @@ -114,6 +153,30 @@ public interface LoadControl { * configured. * @return Whether playback should be allowed to start or resume. */ - boolean shouldStartPlayback( - long bufferedDurationUs, float playbackSpeed, boolean rebuffering, long targetLiveOffsetUs); + @SuppressWarnings("deprecation") // Calling deprecated version of this method. + default boolean shouldStartPlayback( + Timeline timeline, + MediaPeriodId mediaPeriodId, + long bufferedDurationUs, + float playbackSpeed, + boolean rebuffering, + long targetLiveOffsetUs) { + return shouldStartPlayback(bufferedDurationUs, playbackSpeed, rebuffering, targetLiveOffsetUs); + } + + /** + * @deprecated Implement {@link #shouldStartPlayback(Timeline, MediaPeriodId, long, float, + * boolean, long)} instead. + */ + @Deprecated + default boolean shouldStartPlayback( + long bufferedDurationUs, float playbackSpeed, boolean rebuffering, long targetLiveOffsetUs) { + return shouldStartPlayback( + Timeline.EMPTY, + EMPTY_MEDIA_PERIOD_ID, + bufferedDurationUs, + playbackSpeed, + rebuffering, + targetLiveOffsetUs); + } } 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 2700ab4f85..6bd4754f75 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/DefaultLoadControlTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/DefaultLoadControlTest.java @@ -18,6 +18,7 @@ package androidx.media3.exoplayer; import static com.google.common.truth.Truth.assertThat; import androidx.media3.common.C; +import androidx.media3.common.Timeline; import androidx.media3.common.util.Util; import androidx.media3.exoplayer.DefaultLoadControl.Builder; import androidx.media3.exoplayer.source.TrackGroupArray; @@ -179,7 +180,12 @@ public class DefaultLoadControlTest { @Test public void shouldContinueLoading_withNoSelectedTracks_returnsTrue() { loadControl = builder.build(); - loadControl.onTracksSelected(new Renderer[0], TrackGroupArray.EMPTY, new ExoTrackSelection[0]); + loadControl.onTracksSelected( + Timeline.EMPTY, + LoadControl.EMPTY_MEDIA_PERIOD_ID, + new Renderer[0], + TrackGroupArray.EMPTY, + new ExoTrackSelection[0]); assertThat( loadControl.shouldContinueLoading( @@ -203,6 +209,8 @@ public class DefaultLoadControlTest { assertThat( loadControl.shouldStartPlayback( + Timeline.EMPTY, + LoadControl.EMPTY_MEDIA_PERIOD_ID, MIN_BUFFER_US, SPEED, /* rebuffering= */ false, @@ -222,6 +230,8 @@ public class DefaultLoadControlTest { assertThat( loadControl.shouldStartPlayback( + Timeline.EMPTY, + LoadControl.EMPTY_MEDIA_PERIOD_ID, /* bufferedDurationUs= */ 2_999_999, SPEED, /* rebuffering= */ false, @@ -229,6 +239,8 @@ public class DefaultLoadControlTest { .isFalse(); assertThat( loadControl.shouldStartPlayback( + Timeline.EMPTY, + LoadControl.EMPTY_MEDIA_PERIOD_ID, /* bufferedDurationUs= */ 3_000_000, SPEED, /* rebuffering= */ false, @@ -247,6 +259,8 @@ public class DefaultLoadControlTest { assertThat( loadControl.shouldStartPlayback( + Timeline.EMPTY, + LoadControl.EMPTY_MEDIA_PERIOD_ID, /* bufferedDurationUs= */ 499_999, SPEED, /* rebuffering= */ true, @@ -254,6 +268,8 @@ public class DefaultLoadControlTest { .isFalse(); assertThat( loadControl.shouldStartPlayback( + Timeline.EMPTY, + LoadControl.EMPTY_MEDIA_PERIOD_ID, /* bufferedDurationUs= */ 500_000, SPEED, /* rebuffering= */ true, @@ -273,6 +289,8 @@ public class DefaultLoadControlTest { assertThat( loadControl.shouldStartPlayback( + Timeline.EMPTY, + LoadControl.EMPTY_MEDIA_PERIOD_ID, /* bufferedDurationUs= */ 3_999_999, SPEED, /* rebuffering= */ true, @@ -280,6 +298,8 @@ public class DefaultLoadControlTest { .isFalse(); assertThat( loadControl.shouldStartPlayback( + Timeline.EMPTY, + LoadControl.EMPTY_MEDIA_PERIOD_ID, /* bufferedDurationUs= */ 4_000_000, SPEED, /* rebuffering= */ true, @@ -298,6 +318,8 @@ public class DefaultLoadControlTest { assertThat( loadControl.shouldStartPlayback( + Timeline.EMPTY, + LoadControl.EMPTY_MEDIA_PERIOD_ID, /* bufferedDurationUs= */ 499_999, SPEED, /* rebuffering= */ true, @@ -305,6 +327,8 @@ public class DefaultLoadControlTest { .isFalse(); assertThat( loadControl.shouldStartPlayback( + Timeline.EMPTY, + LoadControl.EMPTY_MEDIA_PERIOD_ID, /* bufferedDurationUs= */ 500_000, SPEED, /* rebuffering= */ true, @@ -315,7 +339,8 @@ public class DefaultLoadControlTest { private void build() { builder.setAllocator(allocator).setTargetBufferBytes(TARGET_BUFFER_BYTES); loadControl = builder.build(); - loadControl.onTracksSelected(new Renderer[0], null, null); + loadControl.onTracksSelected( + Timeline.EMPTY, LoadControl.EMPTY_MEDIA_PERIOD_ID, new Renderer[0], null, null); } private void makeSureTargetBufferBytesReached() {