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();
+ }
}