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
This commit is contained in:
lpribanic 2023-11-13 07:52:52 -08:00 committed by Copybara-Service
parent 330713f687
commit 2fb5695a82
9 changed files with 102 additions and 8 deletions

View File

@ -14,6 +14,10 @@
preparing and caching the period, selecting tracks and loading the data preparing and caching the period, selecting tracks and loading the data
on the period. Apps are able to control the preload progress by on the period. Apps are able to control the preload progress by
implementing `PreloadMediaSource.PreloadControl`. 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: * Transformer:
* Add support for flattening H.265/HEVC SEF slow motion videos. * Add support for flattening H.265/HEVC SEF slow motion videos.
* Track Selection: * Track Selection:

View File

@ -30,6 +30,8 @@ import androidx.media3.exoplayer.audio.AudioRendererEventListener;
import androidx.media3.exoplayer.audio.AudioSink; import androidx.media3.exoplayer.audio.AudioSink;
import androidx.media3.exoplayer.audio.DefaultAudioSink; import androidx.media3.exoplayer.audio.DefaultAudioSink;
import androidx.media3.exoplayer.audio.MediaCodecAudioRenderer; 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.DefaultMediaCodecAdapterFactory;
import androidx.media3.exoplayer.mediacodec.MediaCodecAdapter; import androidx.media3.exoplayer.mediacodec.MediaCodecAdapter;
import androidx.media3.exoplayer.mediacodec.MediaCodecSelector; import androidx.media3.exoplayer.mediacodec.MediaCodecSelector;
@ -307,6 +309,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
extensionRendererMode, extensionRendererMode,
renderersList); renderersList);
buildCameraMotionRenderers(context, extensionRendererMode, renderersList); buildCameraMotionRenderers(context, extensionRendererMode, renderersList);
buildImageRenderers(renderersList);
buildMiscellaneousRenderers(context, eventHandler, extensionRendererMode, renderersList); buildMiscellaneousRenderers(context, eventHandler, extensionRendererMode, renderersList);
return renderersList.toArray(new Renderer[0]); return renderersList.toArray(new Renderer[0]);
} }
@ -594,6 +597,18 @@ public class DefaultRenderersFactory implements RenderersFactory {
out.add(new CameraMotionRenderer()); out.add(new CameraMotionRenderer());
} }
/**
* Builds image renderers for use by the player.
*
* <p>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<Renderer> out) {
out.add(new ImageRenderer(ImageDecoder.Factory.DEFAULT, /* imageOutput= */ null));
}
/** /**
* Builds any miscellaneous renderers used by the player. * Builds any miscellaneous renderers used by the player.
* *

View File

@ -56,6 +56,7 @@ import androidx.media3.exoplayer.analytics.AnalyticsListener;
import androidx.media3.exoplayer.analytics.DefaultAnalyticsCollector; import androidx.media3.exoplayer.analytics.DefaultAnalyticsCollector;
import androidx.media3.exoplayer.audio.AudioSink; import androidx.media3.exoplayer.audio.AudioSink;
import androidx.media3.exoplayer.audio.MediaCodecAudioRenderer; import androidx.media3.exoplayer.audio.MediaCodecAudioRenderer;
import androidx.media3.exoplayer.image.ImageOutput;
import androidx.media3.exoplayer.metadata.MetadataRenderer; import androidx.media3.exoplayer.metadata.MetadataRenderer;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.MediaSource;
@ -1821,4 +1822,12 @@ public interface ExoPlayer extends Player {
*/ */
@UnstableApi @UnstableApi
boolean isTunnelingEnabled(); boolean isTunnelingEnabled();
/**
* Sets the {@link ImageOutput} where rendered images will be forwarded.
*
* @param imageOutput The {@link ImageOutput}.
*/
@UnstableApi
void setImageOutput(ImageOutput imageOutput);
} }

View File

@ -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_AUDIO;
import static androidx.media3.common.C.TRACK_TYPE_CAMERA_MOTION; 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.C.TRACK_TYPE_VIDEO;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull; 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_AUX_EFFECT_INFO;
import static androidx.media3.exoplayer.Renderer.MSG_SET_CAMERA_MOTION_LISTENER; 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_PREFERRED_AUDIO_DEVICE; 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_SCALING_MODE;
import static androidx.media3.exoplayer.Renderer.MSG_SET_SKIP_SILENCE_ENABLED; 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.analytics.PlayerId;
import androidx.media3.exoplayer.audio.AudioRendererEventListener; import androidx.media3.exoplayer.audio.AudioRendererEventListener;
import androidx.media3.exoplayer.audio.AudioSink; import androidx.media3.exoplayer.audio.AudioSink;
import androidx.media3.exoplayer.image.ImageOutput;
import androidx.media3.exoplayer.metadata.MetadataOutput; import androidx.media3.exoplayer.metadata.MetadataOutput;
import androidx.media3.exoplayer.source.MaskingMediaSource; import androidx.media3.exoplayer.source.MaskingMediaSource;
import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.MediaSource;
@ -1833,6 +1836,12 @@ import java.util.concurrent.TimeoutException;
return false; return false;
} }
@Override
public void setImageOutput(ImageOutput imageOutput) {
verifyApplicationThread();
sendRendererMessage(TRACK_TYPE_IMAGE, MSG_SET_IMAGE_OUTPUT, imageOutput);
}
@SuppressWarnings("deprecation") // Calling deprecated methods. @SuppressWarnings("deprecation") // Calling deprecated methods.
/* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) { /* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) {
this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread; this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread;

View File

@ -33,6 +33,7 @@ import androidx.media3.common.util.Size;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.analytics.PlayerId; import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.image.ImageOutput;
import androidx.media3.exoplayer.source.MediaPeriod; import androidx.media3.exoplayer.source.MediaPeriod;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId; import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
import androidx.media3.exoplayer.source.SampleStream; 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_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_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_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 * #MSG_SET_AUDIO_SESSION_ID}, {@link #MSG_SET_WAKEUP_LISTENER}, {@link #MSG_SET_VIDEO_EFFECTS},
* {@link #MSG_SET_VIDEO_OUTPUT_RESOLUTION}. May also be an app-defined value (see {@link * {@link #MSG_SET_VIDEO_OUTPUT_RESOLUTION} or {@link #MSG_SET_IMAGE_OUTPUT}. May also be an
* #MSG_CUSTOM_BASE}). * app-defined value (see {@link #MSG_CUSTOM_BASE}).
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@ -116,7 +117,8 @@ public interface Renderer extends PlayerMessage.Target {
MSG_SET_AUDIO_SESSION_ID, MSG_SET_AUDIO_SESSION_ID,
MSG_SET_WAKEUP_LISTENER, MSG_SET_WAKEUP_LISTENER,
MSG_SET_VIDEO_EFFECTS, MSG_SET_VIDEO_EFFECTS,
MSG_SET_VIDEO_OUTPUT_RESOLUTION MSG_SET_VIDEO_OUTPUT_RESOLUTION,
MSG_SET_IMAGE_OUTPUT
}) })
public @interface MessageType {} public @interface MessageType {}
@ -244,6 +246,12 @@ public interface Renderer extends PlayerMessage.Target {
*/ */
int MSG_SET_VIDEO_OUTPUT_RESOLUTION = 14; 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 * 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. * renderers. These custom constants must be greater than or equal to this value.

View File

@ -51,6 +51,7 @@ import androidx.media3.common.util.Size;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.analytics.AnalyticsCollector; import androidx.media3.exoplayer.analytics.AnalyticsCollector;
import androidx.media3.exoplayer.analytics.AnalyticsListener; import androidx.media3.exoplayer.analytics.AnalyticsListener;
import androidx.media3.exoplayer.image.ImageOutput;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.ShuffleOrder; import androidx.media3.exoplayer.source.ShuffleOrder;
@ -1326,6 +1327,12 @@ public class SimpleExoPlayer extends BasePlayer
return player.isTunnelingEnabled(); return player.isTunnelingEnabled();
} }
@Override
public void setImageOutput(ImageOutput imageOutput) {
blockUntilConstructorFinished();
player.setImageOutput(imageOutput);
}
/* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) { /* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) {
blockUntilConstructorFinished(); blockUntilConstructorFinished();
player.setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread); player.setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread);

View File

@ -23,6 +23,20 @@ import androidx.media3.common.util.UnstableApi;
@UnstableApi @UnstableApi
public interface ImageOutput { 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. * Called when an there is a new image available.
* *

View File

@ -84,7 +84,6 @@ public final class ImageRenderer extends BaseRenderer {
private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 3; private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 3;
private final ImageDecoder.Factory decoderFactory; private final ImageDecoder.Factory decoderFactory;
private final ImageOutput imageOutput;
private final DecoderInputBuffer flagsOnlyBuffer; private final DecoderInputBuffer flagsOnlyBuffer;
private final LongArrayQueue offsetQueue; private final LongArrayQueue offsetQueue;
@ -96,6 +95,7 @@ public final class ImageRenderer extends BaseRenderer {
private @Nullable ImageDecoder decoder; private @Nullable ImageDecoder decoder;
private @Nullable DecoderInputBuffer inputBuffer; private @Nullable DecoderInputBuffer inputBuffer;
private @Nullable ImageOutputBuffer outputBuffer; private @Nullable ImageOutputBuffer outputBuffer;
private ImageOutput imageOutput;
/** /**
* Creates an instance. * 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 * @param decoderFactory A {@link ImageDecoder.Factory} that supplies a decoder depending on the
* format provided. * format provided.
* @param imageOutput The rendering component to send the {@link Bitmap} and rendering commands * @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); super(C.TRACK_TYPE_IMAGE);
this.decoderFactory = decoderFactory; this.decoderFactory = decoderFactory;
this.imageOutput = imageOutput; this.imageOutput = getImageOutput(imageOutput);
flagsOnlyBuffer = DecoderInputBuffer.newNoDataInstance(); flagsOnlyBuffer = DecoderInputBuffer.newNoDataInstance();
offsetQueue = new LongArrayQueue(); offsetQueue = new LongArrayQueue();
decoderReinitializationState = REINITIALIZATION_STATE_NONE; decoderReinitializationState = REINITIALIZATION_STATE_NONE;
@ -223,6 +223,20 @@ public final class ImageRenderer extends BaseRenderer {
releaseDecoderResources(); 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, * Attempts to dequeue an output buffer from the decoder and, if successful and permitted to,
* renders it. * renders it.
@ -371,4 +385,12 @@ public final class ImageRenderer extends BaseRenderer {
decoder = null; 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;
}
} }

View File

@ -34,6 +34,7 @@ import androidx.media3.exoplayer.Renderer;
import androidx.media3.exoplayer.SeekParameters; import androidx.media3.exoplayer.SeekParameters;
import androidx.media3.exoplayer.analytics.AnalyticsCollector; import androidx.media3.exoplayer.analytics.AnalyticsCollector;
import androidx.media3.exoplayer.analytics.AnalyticsListener; import androidx.media3.exoplayer.analytics.AnalyticsListener;
import androidx.media3.exoplayer.image.ImageOutput;
import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.ShuffleOrder; import androidx.media3.exoplayer.source.ShuffleOrder;
import androidx.media3.exoplayer.source.TrackGroupArray; import androidx.media3.exoplayer.source.TrackGroupArray;
@ -406,4 +407,9 @@ public class StubExoPlayer extends StubPlayer implements ExoPlayer {
public boolean isTunnelingEnabled() { public boolean isTunnelingEnabled() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public void setImageOutput(ImageOutput imageOutput) {
throw new UnsupportedOperationException();
}
} }