Add ExoPlayer.setPriority

This lets apps update the task manager priority and send the
priority message to all renderers so that they can adjust their
resources if needed.

PiperOrigin-RevId: 629426058
This commit is contained in:
tonihei 2024-04-30 08:46:45 -07:00 committed by Copybara-Service
parent 72013446c4
commit 703b9368c3
6 changed files with 173 additions and 8 deletions

View File

@ -10,6 +10,9 @@
* ExoPlayer:
* Add `reset` to `BasePreloadManager` to release all the holding sources
while keep the preload manager instance.
* Add `ExoPlayer.setPriority` (and `Builder.setPriority`) to define the
priority value used in `PriorityTaskManager` and for MediaCodec
importance from API 35.
* Transformer:
* Work around a decoder bug where the number of audio channels was capped
at stereo when handling PCM input.

View File

@ -456,6 +456,7 @@ public interface ExoPlayer extends Player {
/* package */ Supplier<BandwidthMeter> bandwidthMeterSupplier;
/* package */ Function<Clock, AnalyticsCollector> analyticsCollectorFunction;
/* package */ Looper looper;
/* package */ @C.Priority int priority;
@Nullable /* package */ PriorityTaskManager priorityTaskManager;
/* package */ AudioAttributes audioAttributes;
/* package */ boolean handleAudioFocus;
@ -502,6 +503,7 @@ public interface ExoPlayer extends Player {
* Looper} of the application's main thread if the current thread doesn't have a {@link
* Looper}
* <li>{@link AnalyticsCollector}: {@link AnalyticsCollector} with {@link Clock#DEFAULT}
* <li>{@link C.Priority}: {@link C#PRIORITY_PLAYBACK}
* <li>{@link PriorityTaskManager}: {@code null} (not used)
* <li>{@link AudioAttributes}: {@link AudioAttributes#DEFAULT}, not handling audio focus
* <li>{@link C.WakeMode}: {@link C#WAKE_MODE_NONE}
@ -679,6 +681,7 @@ public interface ExoPlayer extends Player {
detachSurfaceTimeoutMs = DEFAULT_DETACH_SURFACE_TIMEOUT_MS;
usePlatformDiagnostics = true;
playerName = "";
priority = C.PRIORITY_PLAYBACK;
}
/**
@ -837,10 +840,30 @@ public interface ExoPlayer extends Player {
return this;
}
/**
* Sets the {@link C.Priority} for this player.
*
* <p>The priority may influence resource allocation between multiple players or other
* components running in the same app.
*
* <p>This priority is used for the {@link PriorityTaskManager}, if {@linkplain
* #setPriorityTaskManager set}.
*
* @param priority The {@link C.Priority}.
*/
@CanIgnoreReturnValue
@UnstableApi
public Builder setPriority(@C.Priority int priority) {
checkState(!buildCalled);
this.priority = priority;
return this;
}
/**
* Sets an {@link PriorityTaskManager} that will be used by the player.
*
* <p>The priority {@link C#PRIORITY_PLAYBACK} will be set while the player is loading.
* <p>The priority set via {@link #setPriority} (or {@link C#PRIORITY_PLAYBACK by default)} will
* be set while the player is loading.
*
* @param priorityTaskManager A {@link PriorityTaskManager}, or null to not use one.
* @return This builder.
@ -1809,10 +1832,25 @@ public interface ExoPlayer extends Player {
*/
void setWakeMode(@C.WakeMode int wakeMode);
/**
* Sets the {@link C.Priority} for this player.
*
* <p>The priority may influence resource allocation between multiple players or other components
* running in the same app.
*
* <p>This priority is used for the {@link PriorityTaskManager}, if {@linkplain
* #setPriorityTaskManager set}.
*
* @param priority The {@link C.Priority}.
*/
@UnstableApi
void setPriority(@C.Priority int priority);
/**
* Sets a {@link PriorityTaskManager}, or null to clear a previously set priority task manager.
*
* <p>The priority {@link C#PRIORITY_PLAYBACK} will be set while the player is loading.
* <p>The priority set via {@link #setPriority} (or {@link C#PRIORITY_PLAYBACK by default)} will
* be set while the player is loading.
*
* @param priorityTaskManager The {@link PriorityTaskManager}, or null to clear a previously set
* priority task manager.

View File

@ -30,6 +30,7 @@ import static androidx.media3.exoplayer.Renderer.MSG_SET_CAMERA_MOTION_LISTENER;
import static androidx.media3.exoplayer.Renderer.MSG_SET_CHANGE_FRAME_RATE_STRATEGY;
import static androidx.media3.exoplayer.Renderer.MSG_SET_IMAGE_OUTPUT;
import static androidx.media3.exoplayer.Renderer.MSG_SET_PREFERRED_AUDIO_DEVICE;
import static androidx.media3.exoplayer.Renderer.MSG_SET_PRIORITY;
import static androidx.media3.exoplayer.Renderer.MSG_SET_SCALING_MODE;
import static androidx.media3.exoplayer.Renderer.MSG_SET_SKIP_SILENCE_ENABLED;
import static androidx.media3.exoplayer.Renderer.MSG_SET_VIDEO_EFFECTS;
@ -221,6 +222,7 @@ import java.util.concurrent.TimeoutException;
@Nullable private CameraMotionListener cameraMotionListener;
private boolean throwsWhenUsingWrongThread;
private boolean hasNotifiedFullWrongThreadWarning;
private @C.Priority int priority;
@Nullable private PriorityTaskManager priorityTaskManager;
private boolean isPriorityTaskManagerRegistered;
private boolean playerReleased;
@ -255,6 +257,7 @@ import java.util.concurrent.TimeoutException;
+ "]");
applicationContext = builder.context.getApplicationContext();
analyticsCollector = builder.analyticsCollectorFunction.apply(builder.clock);
priority = builder.priority;
priorityTaskManager = builder.priorityTaskManager;
audioAttributes = builder.audioAttributes;
videoScalingMode = builder.videoScalingMode;
@ -433,6 +436,7 @@ import java.util.concurrent.TimeoutException;
TRACK_TYPE_VIDEO, MSG_SET_VIDEO_FRAME_METADATA_LISTENER, frameMetadataListener);
sendRendererMessage(
TRACK_TYPE_CAMERA_MOTION, MSG_SET_CAMERA_MOTION_LISTENER, frameMetadataListener);
sendRendererMessage(MSG_SET_PRIORITY, priority);
} finally {
constructorFinished.open();
}
@ -1075,7 +1079,7 @@ import java.util.concurrent.TimeoutException;
ownedSurface = null;
}
if (isPriorityTaskManagerRegistered) {
checkNotNull(priorityTaskManager).remove(C.PRIORITY_PLAYBACK);
checkNotNull(priorityTaskManager).remove(priority);
isPriorityTaskManagerRegistered = false;
}
currentCueGroup = CueGroup.EMPTY_TIME_ZERO;
@ -1603,6 +1607,21 @@ import java.util.concurrent.TimeoutException;
audioBecomingNoisyManager.setEnabled(handleAudioBecomingNoisy);
}
@Override
public void setPriority(@C.Priority int priority) {
verifyApplicationThread();
if (this.priority == priority) {
return;
}
if (isPriorityTaskManagerRegistered) {
PriorityTaskManager priorityTaskManager = checkNotNull(this.priorityTaskManager);
priorityTaskManager.add(priority);
priorityTaskManager.remove(this.priority);
}
this.priority = priority;
sendRendererMessage(MSG_SET_PRIORITY, priority);
}
@Override
public void setPriorityTaskManager(@Nullable PriorityTaskManager priorityTaskManager) {
verifyApplicationThread();
@ -1610,10 +1629,10 @@ import java.util.concurrent.TimeoutException;
return;
}
if (isPriorityTaskManagerRegistered) {
checkNotNull(this.priorityTaskManager).remove(C.PRIORITY_PLAYBACK);
checkNotNull(this.priorityTaskManager).remove(priority);
}
if (priorityTaskManager != null && isLoading()) {
priorityTaskManager.add(C.PRIORITY_PLAYBACK);
priorityTaskManager.add(priority);
isPriorityTaskManagerRegistered = true;
} else {
isPriorityTaskManagerRegistered = false;
@ -2870,10 +2889,14 @@ import java.util.concurrent.TimeoutException;
}
}
private void sendRendererMessage(int messageType, @Nullable Object payload) {
sendRendererMessage(/* trackType= */ -1, messageType, payload);
}
private void sendRendererMessage(
@C.TrackType int trackType, int messageType, @Nullable Object payload) {
for (Renderer renderer : renderers) {
if (renderer.getTrackType() == trackType) {
if (trackType == -1 || renderer.getTrackType() == trackType) {
createMessageInternal(renderer).setType(messageType).setPayload(payload).send();
}
}
@ -2916,10 +2939,10 @@ import java.util.concurrent.TimeoutException;
private void updatePriorityTaskManagerForIsLoadingChange(boolean isLoading) {
if (priorityTaskManager != null) {
if (isLoading && !isPriorityTaskManagerRegistered) {
priorityTaskManager.add(C.PRIORITY_PLAYBACK);
priorityTaskManager.add(priority);
isPriorityTaskManagerRegistered = true;
} else if (!isLoading && isPriorityTaskManagerRegistered) {
priorityTaskManager.remove(C.PRIORITY_PLAYBACK);
priorityTaskManager.remove(priority);
isPriorityTaskManagerRegistered = false;
}
}

View File

@ -697,6 +697,12 @@ public class SimpleExoPlayer extends BasePlayer
player.setHandleAudioBecomingNoisy(handleAudioBecomingNoisy);
}
@Override
public void setPriority(@C.Priority int priority) {
blockUntilConstructorFinished();
player.setPriority(priority);
}
@Override
public void setPriorityTaskManager(@Nullable PriorityTaskManager priorityTaskManager) {
blockUntilConstructorFinished();

View File

@ -121,6 +121,7 @@ import androidx.media3.common.Player.DiscontinuityReason;
import androidx.media3.common.Player.Listener;
import androidx.media3.common.Player.PlayWhenReadyChangeReason;
import androidx.media3.common.Player.PositionInfo;
import androidx.media3.common.PriorityTaskManager;
import androidx.media3.common.StreamKey;
import androidx.media3.common.Timeline;
import androidx.media3.common.Timeline.Window;
@ -14485,6 +14486,94 @@ public final class ExoPlayerTest {
assertThat(expected).hasMessageThat().contains("lib-effect dependencies");
}
@Test
public void setPriority_blocksOtherLowPriorityTasksInPriorityTaskManager() throws Exception {
PriorityTaskManager priorityTaskManager = new PriorityTaskManager();
ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.setPriorityTaskManager(priorityTaskManager);
player.setPriority(C.PRIORITY_PLAYBACK);
// Add a source without EOS so it loads indefinitely.
player.setMediaSource(
new FakeMediaSource(
new FakeTimeline(),
DrmSessionManager.DRM_UNSUPPORTED,
(format, mediaPeriodId) -> ImmutableList.of(),
ExoPlayerTestRunner.VIDEO_FORMAT));
player.prepare();
run(player).untilPendingCommandsAreFullyHandled();
priorityTaskManager.add(C.PRIORITY_PLAYBACK + 1); // Higher priority than playback.
boolean canProcessOtherTask = priorityTaskManager.proceedNonBlocking(C.PRIORITY_PLAYBACK + 1);
player.setPriority(C.PRIORITY_PLAYBACK + 2);
boolean canProcessOtherTaskAfterUpdate =
priorityTaskManager.proceedNonBlocking(C.PRIORITY_PLAYBACK + 1);
player.release();
boolean canProcessOtherTaskAfterRelease =
priorityTaskManager.proceedNonBlocking(C.PRIORITY_PLAYBACK + 1);
assertThat(canProcessOtherTask).isTrue();
assertThat(canProcessOtherTaskAfterUpdate).isFalse();
assertThat(canProcessOtherTaskAfterRelease).isTrue();
}
@Test
public void setPriority_allowsOtherHighPriorityTasksInPriorityTaskManager() throws Exception {
PriorityTaskManager priorityTaskManager = new PriorityTaskManager();
ExoPlayer player = new TestExoPlayerBuilder(context).build();
player.setPriorityTaskManager(priorityTaskManager);
player.setPriority(C.PRIORITY_PLAYBACK);
// Add a source without EOS so it loads indefinitely.
player.setMediaSource(
new FakeMediaSource(
new FakeTimeline(),
DrmSessionManager.DRM_UNSUPPORTED,
(format, mediaPeriodId) -> ImmutableList.of(),
ExoPlayerTestRunner.VIDEO_FORMAT));
player.prepare();
run(player).untilPendingCommandsAreFullyHandled();
priorityTaskManager.add(C.PRIORITY_PLAYBACK - 1); // Lower priority than playback.
boolean canProcessOtherTask = priorityTaskManager.proceedNonBlocking(C.PRIORITY_PLAYBACK + 1);
player.setPriority(C.PRIORITY_PLAYBACK - 2);
boolean canProcessOtherTaskAfterUpdate =
priorityTaskManager.proceedNonBlocking(C.PRIORITY_PLAYBACK - 1);
player.release();
boolean canProcessOtherTaskAfterRelease =
priorityTaskManager.proceedNonBlocking(C.PRIORITY_PLAYBACK - 1);
assertThat(canProcessOtherTask).isFalse();
assertThat(canProcessOtherTaskAfterUpdate).isTrue();
assertThat(canProcessOtherTaskAfterRelease).isTrue();
}
@Test
public void setPriority_sendsSetPriorityMessageToRenderers() throws Exception {
ArrayList<Pair<Integer, Object>> receivedMessages = new ArrayList<>();
ExoPlayer player =
new TestExoPlayerBuilder(context)
.setRenderers(
new FakeRenderer(C.TRACK_TYPE_VIDEO) {
@Override
public void handleMessage(@MessageType int messageType, @Nullable Object message)
throws ExoPlaybackException {
receivedMessages.add(Pair.create(messageType, message));
super.handleMessage(messageType, message);
}
})
.build();
player.setPriority(C.PRIORITY_DOWNLOAD);
run(player).untilPendingCommandsAreFullyHandled();
player.release();
// Assert default setting and updated setting arrived in the renderer.
assertThat(receivedMessages)
.containsAtLeast(
Pair.create(Renderer.MSG_SET_PRIORITY, C.PRIORITY_PLAYBACK),
Pair.create(Renderer.MSG_SET_PRIORITY, C.PRIORITY_DOWNLOAD))
.inOrder();
}
// Internal methods.
private void addWatchAsSystemFeature() {

View File

@ -20,6 +20,7 @@ import android.os.Looper;
import androidx.annotation.Nullable;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.AuxEffectInfo;
import androidx.media3.common.C;
import androidx.media3.common.Effect;
import androidx.media3.common.Format;
import androidx.media3.common.Player;
@ -397,6 +398,11 @@ public class StubExoPlayer extends StubPlayer implements ExoPlayer {
throw new UnsupportedOperationException();
}
@Override
public void setPriority(@C.Priority int priority) {
throw new UnsupportedOperationException();
}
@Override
public void setPriorityTaskManager(@Nullable PriorityTaskManager priorityTaskManager) {
throw new UnsupportedOperationException();