From 2fb5695a82879e2bb33b12b354bc1ee4905a1c36 Mon Sep 17 00:00:00 2001 From: lpribanic Date: Mon, 13 Nov 2023 07:52:52 -0800 Subject: [PATCH] Instantiate an ImageRenderer Instantiate an ImageRenderer and add it to the list returned by DefaultRenderersFactory.buildRenderers. Add Renderer.MessageType.MSG_SET_IMAGE_OUTPUT and ExoPlayer.setImageOutput to enable setting a custom ImageRenderer.imageOutput. Add ImageRenderer.handleMessage to process messages sent to the renderer. PiperOrigin-RevId: 581962451 --- RELEASENOTES.md | 4 +++ .../exoplayer/DefaultRenderersFactory.java | 15 ++++++++++ .../androidx/media3/exoplayer/ExoPlayer.java | 9 ++++++ .../media3/exoplayer/ExoPlayerImpl.java | 9 ++++++ .../androidx/media3/exoplayer/Renderer.java | 16 +++++++--- .../media3/exoplayer/SimpleExoPlayer.java | 7 +++++ .../media3/exoplayer/image/ImageOutput.java | 14 +++++++++ .../media3/exoplayer/image/ImageRenderer.java | 30 ++++++++++++++++--- .../media3/test/utils/StubExoPlayer.java | 6 ++++ 9 files changed, 102 insertions(+), 8 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4bfefb5fb0..c48192f313 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -14,6 +14,10 @@ preparing and caching the period, selecting tracks and loading the data on the period. Apps are able to control the preload progress by implementing `PreloadMediaSource.PreloadControl`. + * Add `ExoPlayer.setImageOutput` that allows apps to set + `ImageRenderer.ImageOutput`. + * `DefaultRenderersFactory` now provides an `ImageRenderer` to the player + by default with null `ImageOutput` and `ImageDecoder.Factory.DEFAULT`. * Transformer: * Add support for flattening H.265/HEVC SEF slow motion videos. * Track Selection: diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java index ea7b0fd029..a4894b30e2 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java @@ -30,6 +30,8 @@ import androidx.media3.exoplayer.audio.AudioRendererEventListener; import androidx.media3.exoplayer.audio.AudioSink; import androidx.media3.exoplayer.audio.DefaultAudioSink; import androidx.media3.exoplayer.audio.MediaCodecAudioRenderer; +import androidx.media3.exoplayer.image.ImageDecoder; +import androidx.media3.exoplayer.image.ImageRenderer; import androidx.media3.exoplayer.mediacodec.DefaultMediaCodecAdapterFactory; import androidx.media3.exoplayer.mediacodec.MediaCodecAdapter; import androidx.media3.exoplayer.mediacodec.MediaCodecSelector; @@ -307,6 +309,7 @@ public class DefaultRenderersFactory implements RenderersFactory { extensionRendererMode, renderersList); buildCameraMotionRenderers(context, extensionRendererMode, renderersList); + buildImageRenderers(renderersList); buildMiscellaneousRenderers(context, eventHandler, extensionRendererMode, renderersList); return renderersList.toArray(new Renderer[0]); } @@ -594,6 +597,18 @@ public class DefaultRenderersFactory implements RenderersFactory { out.add(new CameraMotionRenderer()); } + /** + * Builds image renderers for use by the player. + * + *

The {@link ImageRenderer} is built with {@code ImageOutput} set to null and {@link + * ImageDecoder.Factory} set to {@code ImageDecoder.Factory.DEFAULT} by default. + * + * @param out An array to which the built renderers should be appended. + */ + protected void buildImageRenderers(ArrayList out) { + out.add(new ImageRenderer(ImageDecoder.Factory.DEFAULT, /* imageOutput= */ null)); + } + /** * Builds any miscellaneous renderers used by the player. * 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 4839178677..96b47c45f2 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java @@ -56,6 +56,7 @@ import androidx.media3.exoplayer.analytics.AnalyticsListener; import androidx.media3.exoplayer.analytics.DefaultAnalyticsCollector; import androidx.media3.exoplayer.audio.AudioSink; import androidx.media3.exoplayer.audio.MediaCodecAudioRenderer; +import androidx.media3.exoplayer.image.ImageOutput; import androidx.media3.exoplayer.metadata.MetadataRenderer; import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; import androidx.media3.exoplayer.source.MediaSource; @@ -1821,4 +1822,12 @@ public interface ExoPlayer extends Player { */ @UnstableApi boolean isTunnelingEnabled(); + + /** + * Sets the {@link ImageOutput} where rendered images will be forwarded. + * + * @param imageOutput The {@link ImageOutput}. + */ + @UnstableApi + void setImageOutput(ImageOutput imageOutput); } 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 081b433c38..2e304b4684 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java @@ -17,6 +17,7 @@ package androidx.media3.exoplayer; import static androidx.media3.common.C.TRACK_TYPE_AUDIO; import static androidx.media3.common.C.TRACK_TYPE_CAMERA_MOTION; +import static androidx.media3.common.C.TRACK_TYPE_IMAGE; import static androidx.media3.common.C.TRACK_TYPE_VIDEO; import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkNotNull; @@ -27,6 +28,7 @@ import static androidx.media3.exoplayer.Renderer.MSG_SET_AUDIO_SESSION_ID; import static androidx.media3.exoplayer.Renderer.MSG_SET_AUX_EFFECT_INFO; 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_SCALING_MODE; import static androidx.media3.exoplayer.Renderer.MSG_SET_SKIP_SILENCE_ENABLED; @@ -98,6 +100,7 @@ import androidx.media3.exoplayer.analytics.MediaMetricsListener; import androidx.media3.exoplayer.analytics.PlayerId; import androidx.media3.exoplayer.audio.AudioRendererEventListener; import androidx.media3.exoplayer.audio.AudioSink; +import androidx.media3.exoplayer.image.ImageOutput; import androidx.media3.exoplayer.metadata.MetadataOutput; import androidx.media3.exoplayer.source.MaskingMediaSource; import androidx.media3.exoplayer.source.MediaSource; @@ -1833,6 +1836,12 @@ import java.util.concurrent.TimeoutException; return false; } + @Override + public void setImageOutput(ImageOutput imageOutput) { + verifyApplicationThread(); + sendRendererMessage(TRACK_TYPE_IMAGE, MSG_SET_IMAGE_OUTPUT, imageOutput); + } + @SuppressWarnings("deprecation") // Calling deprecated methods. /* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) { this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread; diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/Renderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/Renderer.java index baa7019654..dda093c407 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/Renderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/Renderer.java @@ -33,6 +33,7 @@ import androidx.media3.common.util.Size; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import androidx.media3.exoplayer.analytics.PlayerId; +import androidx.media3.exoplayer.image.ImageOutput; import androidx.media3.exoplayer.source.MediaPeriod; import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId; import androidx.media3.exoplayer.source.SampleStream; @@ -94,9 +95,9 @@ public interface Renderer extends PlayerMessage.Target { * #MSG_SET_SCALING_MODE}, {@link #MSG_SET_CHANGE_FRAME_RATE_STRATEGY}, {@link * #MSG_SET_AUX_EFFECT_INFO}, {@link #MSG_SET_VIDEO_FRAME_METADATA_LISTENER}, {@link * #MSG_SET_CAMERA_MOTION_LISTENER}, {@link #MSG_SET_SKIP_SILENCE_ENABLED}, {@link - * #MSG_SET_AUDIO_SESSION_ID}, {@link #MSG_SET_WAKEUP_LISTENER}, {@link #MSG_SET_VIDEO_EFFECTS} or - * {@link #MSG_SET_VIDEO_OUTPUT_RESOLUTION}. May also be an app-defined value (see {@link - * #MSG_CUSTOM_BASE}). + * #MSG_SET_AUDIO_SESSION_ID}, {@link #MSG_SET_WAKEUP_LISTENER}, {@link #MSG_SET_VIDEO_EFFECTS}, + * {@link #MSG_SET_VIDEO_OUTPUT_RESOLUTION} or {@link #MSG_SET_IMAGE_OUTPUT}. May also be an + * app-defined value (see {@link #MSG_CUSTOM_BASE}). */ @Documented @Retention(RetentionPolicy.SOURCE) @@ -116,7 +117,8 @@ public interface Renderer extends PlayerMessage.Target { MSG_SET_AUDIO_SESSION_ID, MSG_SET_WAKEUP_LISTENER, MSG_SET_VIDEO_EFFECTS, - MSG_SET_VIDEO_OUTPUT_RESOLUTION + MSG_SET_VIDEO_OUTPUT_RESOLUTION, + MSG_SET_IMAGE_OUTPUT }) public @interface MessageType {} @@ -244,6 +246,12 @@ public interface Renderer extends PlayerMessage.Target { */ int MSG_SET_VIDEO_OUTPUT_RESOLUTION = 14; + /** + * The type of message that can be passed to an image renderer to set a desired image output. The + * message payload should be an {@link ImageOutput}. + */ + int MSG_SET_IMAGE_OUTPUT = 15; + /** * Applications or extensions may define custom {@code MSG_*} constants that can be passed to * renderers. These custom constants must be greater than or equal to this value. 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 a290d30afe..353ab3f7af 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SimpleExoPlayer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SimpleExoPlayer.java @@ -51,6 +51,7 @@ import androidx.media3.common.util.Size; import androidx.media3.common.util.UnstableApi; import androidx.media3.exoplayer.analytics.AnalyticsCollector; import androidx.media3.exoplayer.analytics.AnalyticsListener; +import androidx.media3.exoplayer.image.ImageOutput; import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.ShuffleOrder; @@ -1326,6 +1327,12 @@ public class SimpleExoPlayer extends BasePlayer return player.isTunnelingEnabled(); } + @Override + public void setImageOutput(ImageOutput imageOutput) { + blockUntilConstructorFinished(); + player.setImageOutput(imageOutput); + } + /* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) { blockUntilConstructorFinished(); player.setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread); diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/ImageOutput.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/ImageOutput.java index 6bd40a46b3..2dc499dcc3 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/ImageOutput.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/ImageOutput.java @@ -23,6 +23,20 @@ import androidx.media3.common.util.UnstableApi; @UnstableApi public interface ImageOutput { + /** A no-op implementation of ImageOutput. */ + ImageOutput NO_OP = + new ImageOutput() { + @Override + public void onImageAvailable(long presentationTimeUs, Bitmap bitmap) { + // Do nothing. + } + + @Override + public void onDisabled() { + // Do nothing. + } + }; + /** * Called when an there is a new image available. * diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/ImageRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/ImageRenderer.java index 3b53dea363..345e2161e7 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/ImageRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/image/ImageRenderer.java @@ -84,7 +84,6 @@ public final class ImageRenderer extends BaseRenderer { private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 3; private final ImageDecoder.Factory decoderFactory; - private final ImageOutput imageOutput; private final DecoderInputBuffer flagsOnlyBuffer; private final LongArrayQueue offsetQueue; @@ -96,6 +95,7 @@ public final class ImageRenderer extends BaseRenderer { private @Nullable ImageDecoder decoder; private @Nullable DecoderInputBuffer inputBuffer; private @Nullable ImageOutputBuffer outputBuffer; + private ImageOutput imageOutput; /** * Creates an instance. @@ -103,12 +103,12 @@ public final class ImageRenderer extends BaseRenderer { * @param decoderFactory A {@link ImageDecoder.Factory} that supplies a decoder depending on the * format provided. * @param imageOutput The rendering component to send the {@link Bitmap} and rendering commands - * to. + * to, or {@code null} if no bitmap output is required. */ - public ImageRenderer(ImageDecoder.Factory decoderFactory, ImageOutput imageOutput) { + public ImageRenderer(ImageDecoder.Factory decoderFactory, @Nullable ImageOutput imageOutput) { super(C.TRACK_TYPE_IMAGE); this.decoderFactory = decoderFactory; - this.imageOutput = imageOutput; + this.imageOutput = getImageOutput(imageOutput); flagsOnlyBuffer = DecoderInputBuffer.newNoDataInstance(); offsetQueue = new LongArrayQueue(); decoderReinitializationState = REINITIALIZATION_STATE_NONE; @@ -223,6 +223,20 @@ public final class ImageRenderer extends BaseRenderer { releaseDecoderResources(); } + @Override + public void handleMessage(@MessageType int messageType, @Nullable Object message) + throws ExoPlaybackException { + switch (messageType) { + case MSG_SET_IMAGE_OUTPUT: + @Nullable ImageOutput imageOutput = + message instanceof ImageOutput ? (ImageOutput) message : null; + setImageOutput(imageOutput); + break; + default: + super.handleMessage(messageType, message); + } + } + /** * Attempts to dequeue an output buffer from the decoder and, if successful and permitted to, * renders it. @@ -371,4 +385,12 @@ public final class ImageRenderer extends BaseRenderer { decoder = null; } } + + private void setImageOutput(@Nullable ImageOutput imageOutput) { + this.imageOutput = getImageOutput(imageOutput); + } + + private static ImageOutput getImageOutput(@Nullable ImageOutput imageOutput) { + return imageOutput == null ? ImageOutput.NO_OP : imageOutput; + } } 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 1787e405fa..2b800baa20 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 @@ -34,6 +34,7 @@ import androidx.media3.exoplayer.Renderer; import androidx.media3.exoplayer.SeekParameters; import androidx.media3.exoplayer.analytics.AnalyticsCollector; import androidx.media3.exoplayer.analytics.AnalyticsListener; +import androidx.media3.exoplayer.image.ImageOutput; import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.ShuffleOrder; import androidx.media3.exoplayer.source.TrackGroupArray; @@ -406,4 +407,9 @@ public class StubExoPlayer extends StubPlayer implements ExoPlayer { public boolean isTunnelingEnabled() { throw new UnsupportedOperationException(); } + + @Override + public void setImageOutput(ImageOutput imageOutput) { + throw new UnsupportedOperationException(); + } }