diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ba28adca08..9ff5ad06af 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -51,6 +51,14 @@ * Add `DefaultPreloadManager.Builder` that builds the `DefaultPreloadManager` and `ExoPlayer` instances with consistently shared configurations. + * Remove `Renderer[]` parameter from `LoadControl.onTracksSelected()` as + `DefaultLoadControl` implementation can retrieve the stream types from + `ExoTrackSelection[]`. + * Deprecated `DefaultLoadControl.calculateTargetBufferBytes(Renderer[], + ExoTrackSelection[])` and marked method as final to prevent overrides. + The new + `DefaultLoadControl.calculateTargetBufferBytes(ExoTrackSelection[])` + should be used instead. * Transformer: * Make setting the image duration using `MediaItem.Builder.setImageDurationMs` mandatory for image export. 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 46323d748b..41518ba442 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultLoadControl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultLoadControl.java @@ -26,6 +26,7 @@ import androidx.media3.common.C; import androidx.media3.common.Timeline; import androidx.media3.common.util.Assertions; import androidx.media3.common.util.Log; +import androidx.media3.common.util.NullableType; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import androidx.media3.exoplayer.analytics.PlayerId; @@ -35,6 +36,7 @@ import androidx.media3.exoplayer.trackselection.ExoTrackSelection; import androidx.media3.exoplayer.upstream.Allocator; import androidx.media3.exoplayer.upstream.DefaultAllocator; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; import java.util.HashMap; /** The default {@link LoadControl} implementation. */ @@ -337,15 +339,12 @@ public class DefaultLoadControl implements LoadControl { @Override public void onTracksSelected( - PlayerId playerId, - Timeline timeline, - MediaPeriodId mediaPeriodId, - Renderer[] renderers, + LoadControl.Parameters parameters, TrackGroupArray trackGroups, - ExoTrackSelection[] trackSelections) { - checkNotNull(loadingStates.get(playerId)).targetBufferBytes = + @NullableType ExoTrackSelection[] trackSelections) { + checkNotNull(loadingStates.get(parameters.playerId)).targetBufferBytes = targetBufferBytesOverwrite == C.LENGTH_UNSET - ? calculateTargetBufferBytes(renderers, trackSelections) + ? calculateTargetBufferBytes(trackSelections) : targetBufferBytesOverwrite; updateAllocator(); } @@ -437,21 +436,29 @@ public class DefaultLoadControl implements LoadControl { * Calculate target buffer size in bytes based on the selected tracks. The player will try not to * exceed this target buffer. Only used when {@code targetBufferBytes} is {@link C#LENGTH_UNSET}. * - * @param renderers The renderers for which the track were selected. * @param trackSelectionArray The selected tracks. * @return The target buffer size in bytes. */ - protected int calculateTargetBufferBytes( - Renderer[] renderers, ExoTrackSelection[] trackSelectionArray) { + protected int calculateTargetBufferBytes(@NullableType ExoTrackSelection[] trackSelectionArray) { int targetBufferSize = 0; - for (int i = 0; i < renderers.length; i++) { - if (trackSelectionArray[i] != null) { - targetBufferSize += getDefaultBufferSize(renderers[i].getTrackType()); + for (ExoTrackSelection exoTrackSelection : trackSelectionArray) { + if (exoTrackSelection != null) { + targetBufferSize += getDefaultBufferSize(exoTrackSelection.getTrackGroup().type); } } return max(DEFAULT_MIN_BUFFER_SIZE, targetBufferSize); } + /** + * @deprecated Use {@link #calculateTargetBufferBytes(ExoTrackSelection[])} instead. + */ + @InlineMe(replacement = "this.calculateTargetBufferBytes(trackSelectionArray)") + @Deprecated + protected final int calculateTargetBufferBytes( + Renderer[] renderers, ExoTrackSelection[] trackSelectionArray) { + return calculateTargetBufferBytes(trackSelectionArray); + } + @VisibleForTesting /* package */ int calculateTotalTargetBufferBytes() { int totalTargetBufferBytes = 0; @@ -503,6 +510,7 @@ public class DefaultLoadControl implements LoadControl { case C.TRACK_TYPE_NONE: return 0; case C.TRACK_TYPE_UNKNOWN: + return DEFAULT_MIN_BUFFER_SIZE; default: throw new IllegalArgumentException(); } 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 f8e0031d8e..6f0accaa4e 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java @@ -2008,6 +2008,7 @@ import java.util.concurrent.atomic.AtomicBoolean; } // Get updated buffered duration as it may have changed since the start of the renderer loop. long bufferedDurationUs = getTotalBufferedDurationUs(loadingHolder.getBufferedPositionUs()); + return loadControl.shouldStartPlayback( new LoadControl.Parameters( playerId, @@ -2907,11 +2908,29 @@ import java.util.concurrent.atomic.AtomicBoolean; MediaPeriodId mediaPeriodId, TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) { + MediaPeriodHolder loadingPeriodHolder = checkNotNull(queue.getLoadingPeriod()); + long playbackPositionUs = + loadingPeriodHolder == queue.getPlayingPeriod() + ? loadingPeriodHolder.toPeriodTime(rendererPositionUs) + : loadingPeriodHolder.toPeriodTime(rendererPositionUs) + - loadingPeriodHolder.info.startPositionUs; + long bufferedDurationUs = + getTotalBufferedDurationUs(loadingPeriodHolder.getBufferedPositionUs()); + long targetLiveOffsetUs = + shouldUseLivePlaybackSpeedControl(playbackInfo.timeline, loadingPeriodHolder.info.id) + ? livePlaybackSpeedControl.getTargetLiveOffsetUs() + : C.TIME_UNSET; loadControl.onTracksSelected( - playerId, - playbackInfo.timeline, - mediaPeriodId, - renderers, + new LoadControl.Parameters( + playerId, + playbackInfo.timeline, + mediaPeriodId, + playbackPositionUs, + bufferedDurationUs, + mediaClock.getPlaybackParameters().speed, + playbackInfo.playWhenReady, + isRebuffering, + targetLiveOffsetUs), trackGroups, trackSelectorResult.selections); } 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 1ae9ccb89e..758b392c42 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/LoadControl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/LoadControl.java @@ -20,6 +20,7 @@ import androidx.media3.common.Player; import androidx.media3.common.Timeline; import androidx.media3.common.TrackGroup; import androidx.media3.common.util.Log; +import androidx.media3.common.util.NullableType; import androidx.media3.common.util.UnstableApi; import androidx.media3.exoplayer.analytics.PlayerId; import androidx.media3.exoplayer.source.MediaPeriod; @@ -145,28 +146,40 @@ public interface LoadControl { /** * Called by the player when a track selection occurs. * - * @param playerId The {@linkplain PlayerId ID of the player} that selected tracks. - * @param timeline The current {@link Timeline} in ExoPlayer. - * @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 parameters containing the {@linkplain PlayerId ID of the player}, the current {@link + * Timeline} in ExoPlayer, and the {@link MediaPeriod} for which the selection was made. Will + * be {@link #EMPTY_MEDIA_PERIOD_ID} when {@code timeline} is empty. * @param trackGroups The {@link TrackGroup}s from which the selection was made. * @param trackSelections The track selections that were made. */ + default void onTracksSelected( + Parameters parameters, + TrackGroupArray trackGroups, + @NullableType ExoTrackSelection[] trackSelections) { + // Media3 ExoPlayer will never call this method. This default implementation provides an + // implementation to please the compiler only. + throw new IllegalStateException("onTracksSelected not implemented"); + } + + /** + * @deprecated Implement {@link #onTracksSelected(Parameters, TrackGroupArray, + * ExoTrackSelection[])} instead. + */ @SuppressWarnings("deprecation") // Calling deprecated version of this method. + @Deprecated default void onTracksSelected( PlayerId playerId, Timeline timeline, MediaPeriodId mediaPeriodId, Renderer[] renderers, TrackGroupArray trackGroups, - ExoTrackSelection[] trackSelections) { + @NullableType ExoTrackSelection[] trackSelections) { onTracksSelected(timeline, mediaPeriodId, renderers, trackGroups, trackSelections); } /** - * @deprecated Implement {@link #onTracksSelected(PlayerId, Timeline, MediaPeriodId, Renderer[], - * TrackGroupArray, ExoTrackSelection[])} instead. + * @deprecated Implement {@link #onTracksSelected(Parameters, TrackGroupArray, + * ExoTrackSelection[])} instead. */ @SuppressWarnings("deprecation") // Calling deprecated version of this method. @Deprecated @@ -175,18 +188,20 @@ public interface LoadControl { MediaPeriodId mediaPeriodId, Renderer[] renderers, TrackGroupArray trackGroups, - ExoTrackSelection[] trackSelections) { + @NullableType ExoTrackSelection[] trackSelections) { onTracksSelected(renderers, trackGroups, trackSelections); } /** - * @deprecated Implement {@link #onTracksSelected(PlayerId, Timeline, MediaPeriodId, Renderer[], - * TrackGroupArray, ExoTrackSelection[])} instead. + * @deprecated Implement {@link #onTracksSelected(Parameters, TrackGroupArray, + * ExoTrackSelection[])} instead. */ @SuppressWarnings("deprecation") // Calling deprecated version of this method. @Deprecated default void onTracksSelected( - Renderer[] renderers, TrackGroupArray trackGroups, ExoTrackSelection[] trackSelections) { + Renderer[] renderers, + TrackGroupArray trackGroups, + @NullableType ExoTrackSelection[] trackSelections) { // Media3 ExoPlayer will never call this method. This default implementation provides an // implementation to please the compiler only. throw new IllegalStateException("onTracksSelected not implemented"); 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 5795d4d811..88646fba2d 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/DefaultLoadControlTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/DefaultLoadControlTest.java @@ -32,7 +32,6 @@ import androidx.media3.exoplayer.source.TrackGroupArray; import androidx.media3.exoplayer.trackselection.ExoTrackSelection; import androidx.media3.exoplayer.trackselection.FixedTrackSelection; import androidx.media3.exoplayer.upstream.DefaultAllocator; -import androidx.media3.test.utils.FakeRenderer; import androidx.media3.test.utils.FakeTimeline; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; @@ -512,10 +511,16 @@ public class DefaultLoadControlTest { loadControl = builder.build(); loadControl.onPrepared(playerId); loadControl.onTracksSelected( - playerId, - timeline, - mediaPeriodId, - new Renderer[0], + new LoadControl.Parameters( + playerId, + timeline, + mediaPeriodId, + /* playbackPositionUs= */ 0L, + /* bufferedDurationUs= */ 0L, + /* playbackSpeed= */ 1f, + /* playWhenReady= */ false, + /* rebuffering= */ false, + /* targetLiveOffsetUs= */ C.TIME_UNSET), TrackGroupArray.EMPTY, new ExoTrackSelection[0]); @@ -747,24 +752,34 @@ public class DefaultLoadControlTest { TrackGroup videoTrackGroup = new TrackGroup(new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_H264).build()); TrackGroupArray videoTrackGroupArray = new TrackGroupArray(videoTrackGroup); - Renderer[] videoRenderer = new Renderer[] {new FakeRenderer(C.TRACK_TYPE_VIDEO)}; TrackGroup audioTrackGroup = new TrackGroup(new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_AAC).build()); TrackGroupArray audioTrackGroupArray = new TrackGroupArray(audioTrackGroup); - Renderer[] audioRenderer = new Renderer[] {new FakeRenderer(C.TRACK_TYPE_AUDIO)}; loadControl.onTracksSelected( - playerId, - timeline, - mediaPeriodId, - videoRenderer, + new LoadControl.Parameters( + playerId, + timeline, + mediaPeriodId, + /* playbackPositionUs= */ 0, + /* bufferedDurationUs= */ 0, + /* playbackSpeed= */ 1.0f, + /* playWhenReady= */ false, + /* rebuffering= */ false, + /* targetLiveOffsetUs= */ C.TIME_UNSET), videoTrackGroupArray, new ExoTrackSelection[] {new FixedTrackSelection(videoTrackGroup, /* track= */ 0)}); loadControl.onTracksSelected( - playerId2, - timeline2, - mediaPeriodId2, - audioRenderer, + new LoadControl.Parameters( + playerId2, + timeline2, + mediaPeriodId2, + /* playbackPositionUs= */ 0, + /* bufferedDurationUs= */ 0, + /* playbackSpeed= */ 1.0f, + /* playWhenReady= */ false, + /* rebuffering= */ false, + /* targetLiveOffsetUs= */ C.TIME_UNSET), audioTrackGroupArray, new ExoTrackSelection[] {new FixedTrackSelection(audioTrackGroup, /* track= */ 0)}); @@ -796,10 +811,16 @@ public class DefaultLoadControlTest { loadControl = builder.build(); loadControl.onPrepared(playerId); loadControl.onTracksSelected( - playerId, - timeline, - mediaPeriodId, - new Renderer[0], + new LoadControl.Parameters( + playerId, + timeline, + mediaPeriodId, + /* playbackPositionUs= */ 0, + /* bufferedDurationUs= */ 0, + /* playbackSpeed= */ 1.0f, + /* playWhenReady= */ false, + /* rebuffering= */ false, + /* targetLiveOffsetUs= */ C.TIME_UNSET), /* trackGroups= */ null, /* trackSelections= */ null); }