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: * ExoPlayer:
* Add `reset` to `BasePreloadManager` to release all the holding sources * Add `reset` to `BasePreloadManager` to release all the holding sources
while keep the preload manager instance. 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: * Transformer:
* Work around a decoder bug where the number of audio channels was capped * Work around a decoder bug where the number of audio channels was capped
at stereo when handling PCM input. at stereo when handling PCM input.

View File

@ -456,6 +456,7 @@ public interface ExoPlayer extends Player {
/* package */ Supplier<BandwidthMeter> bandwidthMeterSupplier; /* package */ Supplier<BandwidthMeter> bandwidthMeterSupplier;
/* package */ Function<Clock, AnalyticsCollector> analyticsCollectorFunction; /* package */ Function<Clock, AnalyticsCollector> analyticsCollectorFunction;
/* package */ Looper looper; /* package */ Looper looper;
/* package */ @C.Priority int priority;
@Nullable /* package */ PriorityTaskManager priorityTaskManager; @Nullable /* package */ PriorityTaskManager priorityTaskManager;
/* package */ AudioAttributes audioAttributes; /* package */ AudioAttributes audioAttributes;
/* package */ boolean handleAudioFocus; /* 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} of the application's main thread if the current thread doesn't have a {@link
* Looper} * Looper}
* <li>{@link AnalyticsCollector}: {@link AnalyticsCollector} with {@link Clock#DEFAULT} * <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 PriorityTaskManager}: {@code null} (not used)
* <li>{@link AudioAttributes}: {@link AudioAttributes#DEFAULT}, not handling audio focus * <li>{@link AudioAttributes}: {@link AudioAttributes#DEFAULT}, not handling audio focus
* <li>{@link C.WakeMode}: {@link C#WAKE_MODE_NONE} * <li>{@link C.WakeMode}: {@link C#WAKE_MODE_NONE}
@ -679,6 +681,7 @@ public interface ExoPlayer extends Player {
detachSurfaceTimeoutMs = DEFAULT_DETACH_SURFACE_TIMEOUT_MS; detachSurfaceTimeoutMs = DEFAULT_DETACH_SURFACE_TIMEOUT_MS;
usePlatformDiagnostics = true; usePlatformDiagnostics = true;
playerName = ""; playerName = "";
priority = C.PRIORITY_PLAYBACK;
} }
/** /**
@ -837,10 +840,30 @@ public interface ExoPlayer extends Player {
return this; 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. * 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. * @param priorityTaskManager A {@link PriorityTaskManager}, or null to not use one.
* @return This builder. * @return This builder.
@ -1809,10 +1832,25 @@ public interface ExoPlayer extends Player {
*/ */
void setWakeMode(@C.WakeMode int wakeMode); 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. * 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 * @param priorityTaskManager The {@link PriorityTaskManager}, or null to clear a previously set
* priority task manager. * 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_CHANGE_FRAME_RATE_STRATEGY;
import static androidx.media3.exoplayer.Renderer.MSG_SET_IMAGE_OUTPUT; 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_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_SCALING_MODE;
import static androidx.media3.exoplayer.Renderer.MSG_SET_SKIP_SILENCE_ENABLED; import static androidx.media3.exoplayer.Renderer.MSG_SET_SKIP_SILENCE_ENABLED;
import static androidx.media3.exoplayer.Renderer.MSG_SET_VIDEO_EFFECTS; import static androidx.media3.exoplayer.Renderer.MSG_SET_VIDEO_EFFECTS;
@ -221,6 +222,7 @@ import java.util.concurrent.TimeoutException;
@Nullable private CameraMotionListener cameraMotionListener; @Nullable private CameraMotionListener cameraMotionListener;
private boolean throwsWhenUsingWrongThread; private boolean throwsWhenUsingWrongThread;
private boolean hasNotifiedFullWrongThreadWarning; private boolean hasNotifiedFullWrongThreadWarning;
private @C.Priority int priority;
@Nullable private PriorityTaskManager priorityTaskManager; @Nullable private PriorityTaskManager priorityTaskManager;
private boolean isPriorityTaskManagerRegistered; private boolean isPriorityTaskManagerRegistered;
private boolean playerReleased; private boolean playerReleased;
@ -255,6 +257,7 @@ import java.util.concurrent.TimeoutException;
+ "]"); + "]");
applicationContext = builder.context.getApplicationContext(); applicationContext = builder.context.getApplicationContext();
analyticsCollector = builder.analyticsCollectorFunction.apply(builder.clock); analyticsCollector = builder.analyticsCollectorFunction.apply(builder.clock);
priority = builder.priority;
priorityTaskManager = builder.priorityTaskManager; priorityTaskManager = builder.priorityTaskManager;
audioAttributes = builder.audioAttributes; audioAttributes = builder.audioAttributes;
videoScalingMode = builder.videoScalingMode; videoScalingMode = builder.videoScalingMode;
@ -433,6 +436,7 @@ import java.util.concurrent.TimeoutException;
TRACK_TYPE_VIDEO, MSG_SET_VIDEO_FRAME_METADATA_LISTENER, frameMetadataListener); TRACK_TYPE_VIDEO, MSG_SET_VIDEO_FRAME_METADATA_LISTENER, frameMetadataListener);
sendRendererMessage( sendRendererMessage(
TRACK_TYPE_CAMERA_MOTION, MSG_SET_CAMERA_MOTION_LISTENER, frameMetadataListener); TRACK_TYPE_CAMERA_MOTION, MSG_SET_CAMERA_MOTION_LISTENER, frameMetadataListener);
sendRendererMessage(MSG_SET_PRIORITY, priority);
} finally { } finally {
constructorFinished.open(); constructorFinished.open();
} }
@ -1075,7 +1079,7 @@ import java.util.concurrent.TimeoutException;
ownedSurface = null; ownedSurface = null;
} }
if (isPriorityTaskManagerRegistered) { if (isPriorityTaskManagerRegistered) {
checkNotNull(priorityTaskManager).remove(C.PRIORITY_PLAYBACK); checkNotNull(priorityTaskManager).remove(priority);
isPriorityTaskManagerRegistered = false; isPriorityTaskManagerRegistered = false;
} }
currentCueGroup = CueGroup.EMPTY_TIME_ZERO; currentCueGroup = CueGroup.EMPTY_TIME_ZERO;
@ -1603,6 +1607,21 @@ import java.util.concurrent.TimeoutException;
audioBecomingNoisyManager.setEnabled(handleAudioBecomingNoisy); 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 @Override
public void setPriorityTaskManager(@Nullable PriorityTaskManager priorityTaskManager) { public void setPriorityTaskManager(@Nullable PriorityTaskManager priorityTaskManager) {
verifyApplicationThread(); verifyApplicationThread();
@ -1610,10 +1629,10 @@ import java.util.concurrent.TimeoutException;
return; return;
} }
if (isPriorityTaskManagerRegistered) { if (isPriorityTaskManagerRegistered) {
checkNotNull(this.priorityTaskManager).remove(C.PRIORITY_PLAYBACK); checkNotNull(this.priorityTaskManager).remove(priority);
} }
if (priorityTaskManager != null && isLoading()) { if (priorityTaskManager != null && isLoading()) {
priorityTaskManager.add(C.PRIORITY_PLAYBACK); priorityTaskManager.add(priority);
isPriorityTaskManagerRegistered = true; isPriorityTaskManagerRegistered = true;
} else { } else {
isPriorityTaskManagerRegistered = false; 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( private void sendRendererMessage(
@C.TrackType int trackType, int messageType, @Nullable Object payload) { @C.TrackType int trackType, int messageType, @Nullable Object payload) {
for (Renderer renderer : renderers) { for (Renderer renderer : renderers) {
if (renderer.getTrackType() == trackType) { if (trackType == -1 || renderer.getTrackType() == trackType) {
createMessageInternal(renderer).setType(messageType).setPayload(payload).send(); createMessageInternal(renderer).setType(messageType).setPayload(payload).send();
} }
} }
@ -2916,10 +2939,10 @@ import java.util.concurrent.TimeoutException;
private void updatePriorityTaskManagerForIsLoadingChange(boolean isLoading) { private void updatePriorityTaskManagerForIsLoadingChange(boolean isLoading) {
if (priorityTaskManager != null) { if (priorityTaskManager != null) {
if (isLoading && !isPriorityTaskManagerRegistered) { if (isLoading && !isPriorityTaskManagerRegistered) {
priorityTaskManager.add(C.PRIORITY_PLAYBACK); priorityTaskManager.add(priority);
isPriorityTaskManagerRegistered = true; isPriorityTaskManagerRegistered = true;
} else if (!isLoading && isPriorityTaskManagerRegistered) { } else if (!isLoading && isPriorityTaskManagerRegistered) {
priorityTaskManager.remove(C.PRIORITY_PLAYBACK); priorityTaskManager.remove(priority);
isPriorityTaskManagerRegistered = false; isPriorityTaskManagerRegistered = false;
} }
} }

View File

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

View File

@ -121,6 +121,7 @@ import androidx.media3.common.Player.DiscontinuityReason;
import androidx.media3.common.Player.Listener; import androidx.media3.common.Player.Listener;
import androidx.media3.common.Player.PlayWhenReadyChangeReason; import androidx.media3.common.Player.PlayWhenReadyChangeReason;
import androidx.media3.common.Player.PositionInfo; import androidx.media3.common.Player.PositionInfo;
import androidx.media3.common.PriorityTaskManager;
import androidx.media3.common.StreamKey; import androidx.media3.common.StreamKey;
import androidx.media3.common.Timeline; import androidx.media3.common.Timeline;
import androidx.media3.common.Timeline.Window; import androidx.media3.common.Timeline.Window;
@ -14485,6 +14486,94 @@ public final class ExoPlayerTest {
assertThat(expected).hasMessageThat().contains("lib-effect dependencies"); 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. // Internal methods.
private void addWatchAsSystemFeature() { private void addWatchAsSystemFeature() {

View File

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