From 703b9368c323c613f4f36f06c4103aabc9969d1c Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 30 Apr 2024 08:46:45 -0700 Subject: [PATCH] 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 --- RELEASENOTES.md | 3 + .../androidx/media3/exoplayer/ExoPlayer.java | 42 ++++++++- .../media3/exoplayer/ExoPlayerImpl.java | 35 ++++++-- .../media3/exoplayer/SimpleExoPlayer.java | 6 ++ .../media3/exoplayer/ExoPlayerTest.java | 89 +++++++++++++++++++ .../media3/test/utils/StubExoPlayer.java | 6 ++ 6 files changed, 173 insertions(+), 8 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f4831003c2..8e452b6372 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -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. diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java index 1b07af4dc0..bdfb96014c 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java @@ -456,6 +456,7 @@ public interface ExoPlayer extends Player { /* package */ Supplier bandwidthMeterSupplier; /* package */ Function 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} *
  • {@link AnalyticsCollector}: {@link AnalyticsCollector} with {@link Clock#DEFAULT} + *
  • {@link C.Priority}: {@link C#PRIORITY_PLAYBACK} *
  • {@link PriorityTaskManager}: {@code null} (not used) *
  • {@link AudioAttributes}: {@link AudioAttributes#DEFAULT}, not handling audio focus *
  • {@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. + * + *

    The priority may influence resource allocation between multiple players or other + * components running in the same app. + * + *

    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. * - *

    The priority {@link C#PRIORITY_PLAYBACK} will be set while the player is loading. + *

    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. + * + *

    The priority may influence resource allocation between multiple players or other components + * running in the same app. + * + *

    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. * - *

    The priority {@link C#PRIORITY_PLAYBACK} will be set while the player is loading. + *

    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. diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java index 8a2b6466d9..110fbeacf0 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java @@ -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; } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SimpleExoPlayer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SimpleExoPlayer.java index 79c370c20e..7b40884687 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SimpleExoPlayer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SimpleExoPlayer.java @@ -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(); diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java index ceec0b970c..840db10c2c 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java @@ -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> 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() { diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/StubExoPlayer.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/StubExoPlayer.java index 8a6301530d..2900aeb040 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/StubExoPlayer.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/StubExoPlayer.java @@ -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();