Allow associating LoadControl methods with the relevant MediaPeriod.

PiperOrigin-RevId: 520037412
This commit is contained in:
Googler 2023-03-28 15:38:11 +00:00 committed by Tianyi Feng
parent 1dbb19a44c
commit f88280dc78
5 changed files with 128 additions and 13 deletions

View File

@ -13,6 +13,9 @@
`SampleQueue.sourceId` and `SampleQueue.peekSourceId`. `SampleQueue.sourceId` and `SampleQueue.peekSourceId`.
* Reset target live stream override when seeking to default position * Reset target live stream override when seeking to default position
([#11051](https://github.com/google/ExoPlayer/pull/11051)). ([#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: * Audio:
* Fix bug where some playbacks fail when tunneling is enabled and * Fix bug where some playbacks fail when tunneling is enabled and
`AudioProcessors` are active, e.g. for gapless trimming `AudioProcessors` are active, e.g. for gapless trimming

View File

@ -21,6 +21,8 @@ import static java.lang.Math.min;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.C; 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.Assertions;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
@ -330,7 +332,11 @@ public class DefaultLoadControl implements LoadControl {
@Override @Override
public void onTracksSelected( public void onTracksSelected(
Renderer[] renderers, TrackGroupArray trackGroups, ExoTrackSelection[] trackSelections) { Timeline timeline,
MediaPeriodId mediaPeriodId,
Renderer[] renderers,
TrackGroupArray trackGroups,
ExoTrackSelection[] trackSelections) {
targetBufferBytes = targetBufferBytes =
targetBufferBytesOverwrite == C.LENGTH_UNSET targetBufferBytesOverwrite == C.LENGTH_UNSET
? calculateTargetBufferBytes(renderers, trackSelections) ? calculateTargetBufferBytes(renderers, trackSelections)
@ -392,7 +398,12 @@ public class DefaultLoadControl implements LoadControl {
@Override @Override
public boolean shouldStartPlayback( 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); bufferedDurationUs = Util.getPlayoutDurationForMediaDuration(bufferedDurationUs, playbackSpeed);
long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs; long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs;
if (targetLiveOffsetUs != C.TIME_UNSET) { if (targetLiveOffsetUs != C.TIME_UNSET) {

View File

@ -1823,8 +1823,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
return true; return true;
} }
// Renderers are ready and we're loading. Ask the LoadControl whether to transition. // Renderers are ready and we're loading. Ask the LoadControl whether to transition.
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
long targetLiveOffsetUs = long targetLiveOffsetUs =
shouldUseLivePlaybackSpeedControl(playbackInfo.timeline, queue.getPlayingPeriod().info.id) shouldUseLivePlaybackSpeedControl(playbackInfo.timeline, playingPeriodHolder.info.id)
? livePlaybackSpeedControl.getTargetLiveOffsetUs() ? livePlaybackSpeedControl.getTargetLiveOffsetUs()
: C.TIME_UNSET; : C.TIME_UNSET;
MediaPeriodHolder loadingHolder = queue.getLoadingPeriod(); MediaPeriodHolder loadingHolder = queue.getLoadingPeriod();
@ -1836,6 +1837,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
return isBufferedToEnd return isBufferedToEnd
|| isAdPendingPreparation || isAdPendingPreparation
|| loadControl.shouldStartPlayback( || loadControl.shouldStartPlayback(
playbackInfo.timeline,
playingPeriodHolder.info.id,
getTotalBufferedDurationUs(), getTotalBufferedDurationUs(),
mediaClock.getPlaybackParameters().speed, mediaClock.getPlaybackParameters().speed,
isRebuffering, isRebuffering,
@ -2291,7 +2294,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
loadingPeriodHolder.handlePrepared( loadingPeriodHolder.handlePrepared(
mediaClock.getPlaybackParameters().speed, playbackInfo.timeline); mediaClock.getPlaybackParameters().speed, playbackInfo.timeline);
updateLoadControlTrackSelection( updateLoadControlTrackSelection(
loadingPeriodHolder.getTrackGroups(), loadingPeriodHolder.getTrackSelectorResult()); loadingPeriodHolder.info.id,
loadingPeriodHolder.getTrackGroups(),
loadingPeriodHolder.getTrackSelectorResult());
if (loadingPeriodHolder == queue.getPlayingPeriod()) { if (loadingPeriodHolder == queue.getPlayingPeriod()) {
// This is the first prepared period, so update the position and the renderers. // This is the first prepared period, so update the position and the renderers.
resetRendererPosition(loadingPeriodHolder.info.startPositionUs); resetRendererPosition(loadingPeriodHolder.info.startPositionUs);
@ -2576,6 +2581,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
&& loadingMediaPeriodHolder != null && loadingMediaPeriodHolder != null
&& loadingMediaPeriodHolder.prepared) { && loadingMediaPeriodHolder.prepared) {
updateLoadControlTrackSelection( updateLoadControlTrackSelection(
loadingMediaPeriodHolder.info.id,
loadingMediaPeriodHolder.getTrackGroups(), loadingMediaPeriodHolder.getTrackGroups(),
loadingMediaPeriodHolder.getTrackSelectorResult()); loadingMediaPeriodHolder.getTrackSelectorResult());
} }
@ -2596,8 +2602,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
private void updateLoadControlTrackSelection( private void updateLoadControlTrackSelection(
TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) { MediaPeriodId mediaPeriodId,
loadControl.onTracksSelected(renderers, trackGroups, trackSelectorResult.selections); TrackGroupArray trackGroups,
TrackSelectorResult trackSelectorResult) {
loadControl.onTracksSelected(
playbackInfo.timeline,
mediaPeriodId,
renderers,
trackGroups,
trackSelectorResult.selections);
} }
private boolean shouldPlayWhenReady() { private boolean shouldPlayWhenReady() {

View File

@ -16,9 +16,11 @@
package androidx.media3.exoplayer; package androidx.media3.exoplayer;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.MediaPeriodId;
import androidx.media3.common.Timeline; import androidx.media3.common.Timeline;
import androidx.media3.common.TrackGroup; import androidx.media3.common.TrackGroup;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.source.MediaPeriod;
import androidx.media3.exoplayer.source.TrackGroupArray; import androidx.media3.exoplayer.source.TrackGroupArray;
import androidx.media3.exoplayer.trackselection.ExoTrackSelection; import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
import androidx.media3.exoplayer.upstream.Allocator; import androidx.media3.exoplayer.upstream.Allocator;
@ -27,18 +29,49 @@ import androidx.media3.exoplayer.upstream.Allocator;
@UnstableApi @UnstableApi
public interface LoadControl { 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. */ /** Called by the player when prepared with a new source. */
void onPrepared(); void onPrepared();
/** /**
* Called by the player when a track selection occurs. * 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 renderers The renderers.
* @param trackGroups The {@link TrackGroup}s from which the selection was made. * @param trackGroups The {@link TrackGroup}s from which the selection was made.
* @param trackSelections The track selections that were made. * @param trackSelections The track selections that were made.
*/ */
void onTracksSelected( @SuppressWarnings("deprecation") // Calling deprecated version of this method.
Renderer[] renderers, TrackGroupArray trackGroups, ExoTrackSelection[] trackSelections); 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. */ /** Called by the player when stopped. */
void onStopped(); void onStopped();
@ -84,7 +117,9 @@ public interface LoadControl {
boolean retainBackBufferFromKeyframe(); 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 * @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 * 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 * 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). * 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 bufferedDurationUs The duration of media that's currently buffered.
* @param playbackSpeed The current factor by which playback is sped up. * @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 * @param rebuffering Whether the player is rebuffering. A rebuffer is defined to be caused by
@ -114,6 +153,30 @@ public interface LoadControl {
* configured. * configured.
* @return Whether playback should be allowed to start or resume. * @return Whether playback should be allowed to start or resume.
*/ */
boolean shouldStartPlayback( @SuppressWarnings("deprecation") // Calling deprecated version of this method.
long bufferedDurationUs, float playbackSpeed, boolean rebuffering, long targetLiveOffsetUs); 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);
}
} }

View File

@ -18,6 +18,7 @@ package androidx.media3.exoplayer;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Timeline;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.DefaultLoadControl.Builder; import androidx.media3.exoplayer.DefaultLoadControl.Builder;
import androidx.media3.exoplayer.source.TrackGroupArray; import androidx.media3.exoplayer.source.TrackGroupArray;
@ -179,7 +180,12 @@ public class DefaultLoadControlTest {
@Test @Test
public void shouldContinueLoading_withNoSelectedTracks_returnsTrue() { public void shouldContinueLoading_withNoSelectedTracks_returnsTrue() {
loadControl = builder.build(); 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( assertThat(
loadControl.shouldContinueLoading( loadControl.shouldContinueLoading(
@ -203,6 +209,8 @@ public class DefaultLoadControlTest {
assertThat( assertThat(
loadControl.shouldStartPlayback( loadControl.shouldStartPlayback(
Timeline.EMPTY,
LoadControl.EMPTY_MEDIA_PERIOD_ID,
MIN_BUFFER_US, MIN_BUFFER_US,
SPEED, SPEED,
/* rebuffering= */ false, /* rebuffering= */ false,
@ -222,6 +230,8 @@ public class DefaultLoadControlTest {
assertThat( assertThat(
loadControl.shouldStartPlayback( loadControl.shouldStartPlayback(
Timeline.EMPTY,
LoadControl.EMPTY_MEDIA_PERIOD_ID,
/* bufferedDurationUs= */ 2_999_999, /* bufferedDurationUs= */ 2_999_999,
SPEED, SPEED,
/* rebuffering= */ false, /* rebuffering= */ false,
@ -229,6 +239,8 @@ public class DefaultLoadControlTest {
.isFalse(); .isFalse();
assertThat( assertThat(
loadControl.shouldStartPlayback( loadControl.shouldStartPlayback(
Timeline.EMPTY,
LoadControl.EMPTY_MEDIA_PERIOD_ID,
/* bufferedDurationUs= */ 3_000_000, /* bufferedDurationUs= */ 3_000_000,
SPEED, SPEED,
/* rebuffering= */ false, /* rebuffering= */ false,
@ -247,6 +259,8 @@ public class DefaultLoadControlTest {
assertThat( assertThat(
loadControl.shouldStartPlayback( loadControl.shouldStartPlayback(
Timeline.EMPTY,
LoadControl.EMPTY_MEDIA_PERIOD_ID,
/* bufferedDurationUs= */ 499_999, /* bufferedDurationUs= */ 499_999,
SPEED, SPEED,
/* rebuffering= */ true, /* rebuffering= */ true,
@ -254,6 +268,8 @@ public class DefaultLoadControlTest {
.isFalse(); .isFalse();
assertThat( assertThat(
loadControl.shouldStartPlayback( loadControl.shouldStartPlayback(
Timeline.EMPTY,
LoadControl.EMPTY_MEDIA_PERIOD_ID,
/* bufferedDurationUs= */ 500_000, /* bufferedDurationUs= */ 500_000,
SPEED, SPEED,
/* rebuffering= */ true, /* rebuffering= */ true,
@ -273,6 +289,8 @@ public class DefaultLoadControlTest {
assertThat( assertThat(
loadControl.shouldStartPlayback( loadControl.shouldStartPlayback(
Timeline.EMPTY,
LoadControl.EMPTY_MEDIA_PERIOD_ID,
/* bufferedDurationUs= */ 3_999_999, /* bufferedDurationUs= */ 3_999_999,
SPEED, SPEED,
/* rebuffering= */ true, /* rebuffering= */ true,
@ -280,6 +298,8 @@ public class DefaultLoadControlTest {
.isFalse(); .isFalse();
assertThat( assertThat(
loadControl.shouldStartPlayback( loadControl.shouldStartPlayback(
Timeline.EMPTY,
LoadControl.EMPTY_MEDIA_PERIOD_ID,
/* bufferedDurationUs= */ 4_000_000, /* bufferedDurationUs= */ 4_000_000,
SPEED, SPEED,
/* rebuffering= */ true, /* rebuffering= */ true,
@ -298,6 +318,8 @@ public class DefaultLoadControlTest {
assertThat( assertThat(
loadControl.shouldStartPlayback( loadControl.shouldStartPlayback(
Timeline.EMPTY,
LoadControl.EMPTY_MEDIA_PERIOD_ID,
/* bufferedDurationUs= */ 499_999, /* bufferedDurationUs= */ 499_999,
SPEED, SPEED,
/* rebuffering= */ true, /* rebuffering= */ true,
@ -305,6 +327,8 @@ public class DefaultLoadControlTest {
.isFalse(); .isFalse();
assertThat( assertThat(
loadControl.shouldStartPlayback( loadControl.shouldStartPlayback(
Timeline.EMPTY,
LoadControl.EMPTY_MEDIA_PERIOD_ID,
/* bufferedDurationUs= */ 500_000, /* bufferedDurationUs= */ 500_000,
SPEED, SPEED,
/* rebuffering= */ true, /* rebuffering= */ true,
@ -315,7 +339,8 @@ public class DefaultLoadControlTest {
private void build() { private void build() {
builder.setAllocator(allocator).setTargetBufferBytes(TARGET_BUFFER_BYTES); builder.setAllocator(allocator).setTargetBufferBytes(TARGET_BUFFER_BYTES);
loadControl = builder.build(); 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() { private void makeSureTargetBufferBytesReached() {