Skip to content

Commit

Permalink
Move parameters inside LoadControl and use it for shouldStartPlayback
Browse files Browse the repository at this point in the history
+additional formatting and Javadoc changes
  • Loading branch information
tonihei committed May 20, 2024
1 parent be5cf6b commit c46bb24
Show file tree
Hide file tree
Showing 7 changed files with 563 additions and 512 deletions.
1 change: 1 addition & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ This release includes the following changes since the
`DefaultPreloadManager` which uses `PreloadMediaSource` to preload media
samples of the sources into memory, and uses an integer `rankingData`
that indicates the index of an item on the UI.
* Use data class for `LoadControl` methods instead of individual parameters.
* Add `PlayerId` to most methods of `LoadControl` to enable `LoadControl`
implementations to support multiple players.
* Remove `Buffer.isDecodeOnly()` and `C.BUFFER_FLAG_DECODE_ONLY`. There is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,47 +379,42 @@ public boolean retainBackBufferFromKeyframe(PlayerId playerId) {
}

@Override
public boolean shouldContinueLoading(LoadParameters loadParameters) {
PlayerLoadingState playerLoadingState =
checkNotNull(loadingStates.get(loadParameters.playerId));
public boolean shouldContinueLoading(Parameters parameters) {
PlayerLoadingState playerLoadingState = checkNotNull(loadingStates.get(parameters.playerId));
boolean targetBufferSizeReached =
allocator.getTotalBytesAllocated() >= calculateTotalTargetBufferBytes();
long minBufferUs = this.minBufferUs;
if (loadParameters.playbackSpeed > 1) {
if (parameters.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, loadParameters.playbackSpeed);
Util.getMediaDurationForPlayoutDuration(minBufferUs, parameters.playbackSpeed);
minBufferUs = min(mediaDurationMinBufferUs, maxBufferUs);
}
// Prevent playback from getting stuck if minBufferUs is too small.
minBufferUs = max(minBufferUs, 500_000);
if (loadParameters.bufferedDurationUs < minBufferUs) {
if (parameters.bufferedDurationUs < minBufferUs) {
playerLoadingState.isLoading = prioritizeTimeOverSizeThresholds || !targetBufferSizeReached;
if (!playerLoadingState.isLoading && loadParameters.bufferedDurationUs < 500_000) {
if (!playerLoadingState.isLoading && parameters.bufferedDurationUs < 500_000) {
Log.w(
"DefaultLoadControl",
"Target buffer size reached with less than 500ms of buffered media data.");
}
} else if (loadParameters.bufferedDurationUs >= maxBufferUs || targetBufferSizeReached) {
} else if (parameters.bufferedDurationUs >= maxBufferUs || targetBufferSizeReached) {
playerLoadingState.isLoading = false;
} // Else don't change the loading state.
return playerLoadingState.isLoading;
}

@Override
public boolean shouldStartPlayback(
PlayerId playerId,
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) {
minBufferDurationUs = min(targetLiveOffsetUs / 2, minBufferDurationUs);
public boolean shouldStartPlayback(Parameters parameters) {
long bufferedDurationUs =
Util.getPlayoutDurationForMediaDuration(
parameters.bufferedDurationUs, parameters.playbackSpeed);
long minBufferDurationUs =
parameters.rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs;
if (parameters.targetLiveOffsetUs != C.TIME_UNSET) {
minBufferDurationUs = min(parameters.targetLiveOffsetUs / 2, minBufferDurationUs);
}
return minBufferDurationUs <= 0
|| bufferedDurationUs >= minBufferDurationUs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1975,13 +1975,16 @@ private boolean shouldTransitionToReadyState(boolean renderersReadyOrEnded) {
return isBufferedToEnd
|| isAdPendingPreparation
|| loadControl.shouldStartPlayback(
playerId,
playbackInfo.timeline,
playingPeriodHolder.info.id,
getTotalBufferedDurationUs(),
mediaClock.getPlaybackParameters().speed,
isRebuffering,
targetLiveOffsetUs);
new LoadControl.Parameters(
playerId,
playbackInfo.timeline,
playingPeriodHolder.info.id,
playingPeriodHolder.toPeriodTime(rendererPositionUs),
getTotalBufferedDurationUs(),
mediaClock.getPlaybackParameters().speed,
playbackInfo.playWhenReady,
isRebuffering,
targetLiveOffsetUs));
}

private boolean isTimelineReady() {
Expand Down Expand Up @@ -2577,14 +2580,21 @@ private boolean shouldContinueLoading() {
? loadingPeriodHolder.toPeriodTime(rendererPositionUs)
: loadingPeriodHolder.toPeriodTime(rendererPositionUs)
- loadingPeriodHolder.info.startPositionUs;
LoadParameters loadParameters = new LoadParameters(
playerId,
playbackInfo.timeline,
loadingPeriodHolder.info.id,
playbackPositionUs,
bufferedDurationUs,
mediaClock.getPlaybackParameters().speed,
playbackInfo.playWhenReady);
long targetLiveOffsetUs =
shouldUseLivePlaybackSpeedControl(playbackInfo.timeline, loadingPeriodHolder.info.id)
? livePlaybackSpeedControl.getTargetLiveOffsetUs()
: C.TIME_UNSET;
LoadControl.Parameters loadParameters =
new LoadControl.Parameters(
playerId,
playbackInfo.timeline,
loadingPeriodHolder.info.id,
playbackPositionUs,
bufferedDurationUs,
mediaClock.getPlaybackParameters().speed,
playbackInfo.playWhenReady,
isRebuffering,
targetLiveOffsetUs);
boolean shouldContinueLoading = loadControl.shouldContinueLoading(loadParameters);
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
if (!shouldContinueLoading
Expand All @@ -2595,8 +2605,7 @@ private boolean shouldContinueLoading() {
// and try again in case it's blocked on memory usage of the back buffer.
playingPeriodHolder.mediaPeriod.discardBuffer(
playbackInfo.positionUs, /* toKeyframe= */ false);
shouldContinueLoading =
loadControl.shouldContinueLoading(loadParameters);
shouldContinueLoading = loadControl.shouldContinueLoading(loadParameters);
}
return shouldContinueLoading;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package androidx.media3.exoplayer;

import androidx.media3.common.C;
import androidx.media3.common.Player;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.util.UnstableApi;
Expand All @@ -30,6 +31,88 @@
@UnstableApi
public interface LoadControl {

/**
* Information about the current playback context and the {@link MediaPeriod} for which {@link
* LoadControl} methods are called.
*/
final class Parameters {
/** The {@linkplain PlayerId ID of the player}. */
public final PlayerId playerId;

/** The current {@link Timeline} of the player. */
public final Timeline timeline;

/**
* The {@link MediaPeriodId} of the affected {@link MediaPeriod} in the current {@link
* #timeline}.
*/
public final MediaPeriodId mediaPeriodId;

/**
* The current playback position in microseconds, relative to the start of the affected {@link
* MediaPeriod} identified by {@link #mediaPeriodId}. 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 total 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;

/**
* 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.
*/
public final boolean rebuffering;

/**
* The desired playback position offset to the live edge in microseconds, or {@link
* C#TIME_UNSET} if the media is not a live stream or no offset is configured.
*/
public final long targetLiveOffsetUs;

/**
* Creates parameters for {@link LoadControl} methods.
*
* @param playerId See {@link #playerId}.
* @param timeline See {@link #timeline}.
* @param mediaPeriodId See {@link #mediaPeriodId}.
* @param playbackPositionUs See {@link #playbackPositionUs}.
* @param bufferedDurationUs See {@link #bufferedDurationUs}.
* @param playbackSpeed See {@link #playbackSpeed}.
* @param playWhenReady See {@link #playWhenReady}.
* @param rebuffering See {@link #rebuffering}.
* @param targetLiveOffsetUs See {@link #targetLiveOffsetUs}.
*/
public Parameters(
PlayerId playerId,
Timeline timeline,
MediaPeriodId mediaPeriodId,
long playbackPositionUs,
long bufferedDurationUs,
float playbackSpeed,
boolean playWhenReady,
boolean rebuffering,
long targetLiveOffsetUs) {
this.playerId = playerId;
this.timeline = timeline;
this.mediaPeriodId = mediaPeriodId;
this.playbackPositionUs = playbackPositionUs;
this.bufferedDurationUs = bufferedDurationUs;
this.playbackSpeed = playbackSpeed;
this.playWhenReady = playWhenReady;
this.rebuffering = rebuffering;
this.targetLiveOffsetUs = targetLiveOffsetUs;
}
}

/**
* @deprecated Used as a placeholder when MediaPeriodId is unknown. Only used when the deprecated
* methods {@link #onTracksSelected(Renderer[], TrackGroupArray, ExoTrackSelection[])} or
Expand Down Expand Up @@ -220,38 +303,18 @@ default boolean retainBackBufferFromKeyframe() {
* returns true, the {@link MediaPeriod} identified in the most recent {@link #onTracksSelected}
* call will continue being loaded.
*
* @param loadParameters Parameters for Load Control. Refer to {@link LoadParameters} for more
* information on the individual parameters
* @param parameters Information about the playback context and the {@link MediaPeriod} that will
* continue to load if this method returns {@code true}.
* @return Whether the loading should continue.
*/
@SuppressWarnings("deprecation")
default boolean shouldContinueLoading(final LoadParameters loadParameters) {
default boolean shouldContinueLoading(Parameters parameters) {
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,
MediaPeriodId mediaPeriodId,
long playbackPositionUs,
long bufferedDurationUs,
float playbackSpeed) {
return shouldContinueLoading(playbackPositionUs, bufferedDurationUs, playbackSpeed);
parameters.playbackPositionUs, parameters.bufferedDurationUs, parameters.playbackSpeed);
}

/**
* @deprecated Implement {@link #shouldContinueLoading(LoadParameters)} instead.
* @deprecated Implement {@link #shouldContinueLoading(Parameters)} instead.
*/
@Deprecated
default boolean shouldContinueLoading(
Expand All @@ -267,43 +330,25 @@ default boolean shouldContinueLoading(
* 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 playerId The {@linkplain PlayerId ID of the player} that wants to start playback.
* @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
* 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.
* @param targetLiveOffsetUs The desired playback position offset to the live edge in
* microseconds, or {@link C#TIME_UNSET} if the media is not a live stream or no offset is
* configured.
* @param parameters Information about the playback context and the {@link MediaPeriod} that will
* start playing if this method returns {@code true}.
* @return Whether playback should be allowed to start or resume.
*/
@SuppressWarnings("deprecation") // Calling deprecated version of this method.
default boolean shouldStartPlayback(
PlayerId playerId,
Timeline timeline,
MediaPeriodId mediaPeriodId,
long bufferedDurationUs,
float playbackSpeed,
boolean rebuffering,
long targetLiveOffsetUs) {
default boolean shouldStartPlayback(Parameters parameters) {
return shouldStartPlayback(
timeline,
mediaPeriodId,
bufferedDurationUs,
playbackSpeed,
rebuffering,
targetLiveOffsetUs);
parameters.timeline,
parameters.mediaPeriodId,
parameters.bufferedDurationUs,
parameters.playbackSpeed,
parameters.rebuffering,
parameters.targetLiveOffsetUs);
}

/**
* @deprecated Implement {@link #shouldStartPlayback(PlayerId, Timeline, MediaPeriodId, long,
* float, boolean, long)} instead.
* @deprecated Implement {@link #shouldStartPlayback(Parameters)} instead.
*/
@SuppressWarnings("deprecation") // Calling deprecated version of this method.
@Deprecated
default boolean shouldStartPlayback(
Timeline timeline,
Expand All @@ -312,14 +357,13 @@ default boolean shouldStartPlayback(
float playbackSpeed,
boolean rebuffering,
long targetLiveOffsetUs) {
// Media3 ExoPlayer will never call this method. This default implementation provides an
// implementation to please the compiler only.
throw new IllegalStateException("shouldStartPlayback not implemented");
// Media3 ExoPlayer will never call this method. The default implementation is only used to
// forward to the deprecated version below.
return shouldStartPlayback(bufferedDurationUs, playbackSpeed, rebuffering, targetLiveOffsetUs);
}

/**
* @deprecated Implement {@link #shouldStartPlayback(PlayerId, Timeline, MediaPeriodId, long,
* float, boolean, long)} instead.
* @deprecated Implement {@link #shouldStartPlayback(Parameters)} instead.
*/
@Deprecated
default boolean shouldStartPlayback(
Expand Down
Loading

0 comments on commit c46bb24

Please sign in to comment.