diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2d9601dbf6..fb47d9543b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -50,11 +50,16 @@ * Change interface of `LoadErrorHandlingPolicy` to support configuring the behavior of track and location fallback. Location fallback is currently only supported for DASH manifests with multiple base URLs. - * Disable platform transcoding when playing content URIs on Android 12. * Restrict use of `AudioTrack.isDirectPlaybackSupported` to TVs, to avoid listing audio offload encodings as supported for passthrough mode on mobile devices ([#9239](https://github.com/google/ExoPlayer/issues/9239)). +* Android 12 compatibility: + * Disable platform transcoding when playing content URIs on Android 12. + * Add `ExoPlayer.setVideoChangeFrameRateStrategy` to allow disabling of + calls from the player to `Surface.setFrameRate`. This is useful for + applications wanting to call `Surface.setFrameRate` directly from + application code with Android 12's `Surface.CHANGE_FRAME_RATE_ALWAYS`. * Remove deprecated symbols: * Remove `Player.getPlaybackError`. Use `Player.getPlayerError` instead. * Remove `Player.getCurrentTag`. Use `Player.getCurrentMediaItem` and diff --git a/library/common/src/main/java/com/google/android/exoplayer2/C.java b/library/common/src/main/java/com/google/android/exoplayer2/C.java index ede2dc6001..4364103ace 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/C.java @@ -21,6 +21,7 @@ import android.media.AudioFormat; import android.media.AudioManager; import android.media.MediaCodec; import android.media.MediaFormat; +import android.view.Surface; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; @@ -491,6 +492,23 @@ public final class C { /** A default video scaling mode for {@link MediaCodec}-based renderers. */ public static final int VIDEO_SCALING_MODE_DEFAULT = VIDEO_SCALING_MODE_SCALE_TO_FIT; + /** Strategies for calling {@link Surface#setFrameRate}. */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({VIDEO_CHANGE_FRAME_RATE_STRATEGY_OFF, VIDEO_CHANGE_FRAME_RATE_STRATEGY_ONLY_IF_SEAMLESS}) + public @interface VideoChangeFrameRateStrategy {} + /** + * Strategy to never call {@link Surface#setFrameRate}. Use this strategy if you prefer to call + * {@link Surface#setFrameRate} directly from application code. + */ + public static final int VIDEO_CHANGE_FRAME_RATE_STRATEGY_OFF = Integer.MIN_VALUE; + /** + * Strategy to call {@link Surface#setFrameRate} with {@link + * Surface#CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS} when the output frame rate is known. + */ + public static final int VIDEO_CHANGE_FRAME_RATE_STRATEGY_ONLY_IF_SEAMLESS = + Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS; + /** * Track selection flags. Possible flag values are {@link #SELECTION_FLAG_DEFAULT}, {@link * #SELECTION_FLAG_FORCED} and {@link #SELECTION_FLAG_AUTOSELECT}. @@ -982,7 +1000,7 @@ public final class C { FORMAT_UNSUPPORTED_SUBTYPE, FORMAT_UNSUPPORTED_TYPE }) - public static @interface FormatSupport {} + public @interface FormatSupport {} // TODO(b/172315872) Renderer was a link. Link to equivalent concept or remove @code. /** The {@code Renderer} is capable of rendering the format. */ public static final int FORMAT_HANDLED = 0b100; @@ -1023,6 +1041,7 @@ public final class C { * audio MIME type. */ public static final int FORMAT_UNSUPPORTED_TYPE = 0b000; + /** * Converts a time in microseconds to the corresponding time in milliseconds, preserving {@link * #TIME_UNSET} and {@link #TIME_END_OF_SOURCE} values. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index cd4d855f1e..17aa270f90 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2; import android.content.Context; import android.media.AudioTrack; +import android.media.MediaCodec; import android.os.Looper; import android.view.Surface; import android.view.SurfaceHolder; @@ -241,6 +242,9 @@ public interface ExoPlayer extends Player { /** * Sets the {@link C.VideoScalingMode}. * + *
The scaling mode only applies if a {@link MediaCodec}-based video {@link Renderer} is + * enabled and if the output surface is owned by a {@link SurfaceView}. + * * @param videoScalingMode The {@link C.VideoScalingMode}. */ void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode); @@ -249,6 +253,25 @@ public interface ExoPlayer extends Player { @C.VideoScalingMode int getVideoScalingMode(); + /** + * Sets a {@link C.VideoChangeFrameRateStrategy} that will be used by the player when provided + * with a video output {@link Surface}. + * + *
The strategy only applies if a {@link MediaCodec}-based video {@link Renderer} is enabled. + * Applications wishing to use {@link Surface#CHANGE_FRAME_RATE_ALWAYS} should set the mode to + * {@link C#VIDEO_CHANGE_FRAME_RATE_STRATEGY_OFF} to disable calls to {@link + * Surface#setFrameRate} from ExoPlayer, and should then call {@link Surface#setFrameRate} + * directly from application code. + * + * @param videoChangeFrameRateStrategy A {@link C.VideoChangeFrameRateStrategy}. + */ + void setVideoChangeFrameRateStrategy( + @C.VideoChangeFrameRateStrategy int videoChangeFrameRateStrategy); + + /** Returns the {@link C.VideoChangeFrameRateStrategy}. */ + @C.VideoChangeFrameRateStrategy + int getVideoChangeFrameRateStrategy(); + /** * Adds a listener to receive video events. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java index 1affb1d088..9c866e531b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java @@ -119,37 +119,43 @@ public interface Renderer extends PlayerMessage.Target { * owned by a {@link android.view.SurfaceView}. */ int MSG_SET_SCALING_MODE = 4; + /** + * The type of a message that can be passed to a video renderer via {@link + * ExoPlayer#createMessage(Target)}. The message payload should be one of the integer strategy + * constants in {@link C.VideoChangeFrameRateStrategy}. + */ + int MSG_SET_CHANGE_FRAME_RATE_STRATEGY = 5; /** * A type of a message that can be passed to an audio renderer via {@link * ExoPlayer#createMessage(Target)}. The message payload should be an {@link AuxEffectInfo} * instance representing an auxiliary audio effect for the underlying audio track. */ - int MSG_SET_AUX_EFFECT_INFO = 5; + int MSG_SET_AUX_EFFECT_INFO = 6; /** * The type of a message that can be passed to a video renderer via {@link * ExoPlayer#createMessage(Target)}. The message payload should be a {@link * VideoFrameMetadataListener} instance, or null. */ - int MSG_SET_VIDEO_FRAME_METADATA_LISTENER = 6; + int MSG_SET_VIDEO_FRAME_METADATA_LISTENER = 7; /** * The type of a message that can be passed to a camera motion renderer via {@link * ExoPlayer#createMessage(Target)}. The message payload should be a {@link CameraMotionListener} * instance, or null. */ - int MSG_SET_CAMERA_MOTION_LISTENER = 7; + int MSG_SET_CAMERA_MOTION_LISTENER = 8; /** * The type of a message that can be passed to an audio renderer via {@link * ExoPlayer#createMessage(Target)}. The message payload should be a {@link Boolean} instance * telling whether to enable or disable skipping silences in the audio stream. */ - int MSG_SET_SKIP_SILENCE_ENABLED = 8; + int MSG_SET_SKIP_SILENCE_ENABLED = 9; /** * The type of a message that can be passed to audio and video renderers via {@link * ExoPlayer#createMessage(Target)}. The message payload should be an {@link Integer} instance * representing the audio session ID that will be attached to the underlying audio track. Video * renderers that support tunneling will use the audio session ID when tunneling is enabled. */ - int MSG_SET_AUDIO_SESSION_ID = 9; + int MSG_SET_AUDIO_SESSION_ID = 10; /** * The type of a message that can be passed to a {@link Renderer} via {@link * ExoPlayer#createMessage(Target)}, to inform the renderer that it can schedule waking up another @@ -157,7 +163,7 @@ public interface Renderer extends PlayerMessage.Target { * *
The message payload must be a {@link WakeupListener} instance. */ - int MSG_SET_WAKEUP_LISTENER = 10; + int MSG_SET_WAKEUP_LISTENER = 11; /** * 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/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 76db88f84f..3acffbca5a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -22,6 +22,7 @@ import static com.google.android.exoplayer2.Renderer.MSG_SET_AUDIO_ATTRIBUTES; import static com.google.android.exoplayer2.Renderer.MSG_SET_AUDIO_SESSION_ID; import static com.google.android.exoplayer2.Renderer.MSG_SET_AUX_EFFECT_INFO; import static com.google.android.exoplayer2.Renderer.MSG_SET_CAMERA_MOTION_LISTENER; +import static com.google.android.exoplayer2.Renderer.MSG_SET_CHANGE_FRAME_RATE_STRATEGY; import static com.google.android.exoplayer2.Renderer.MSG_SET_SCALING_MODE; import static com.google.android.exoplayer2.Renderer.MSG_SET_SKIP_SILENCE_ENABLED; import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER; @@ -130,6 +131,7 @@ public class SimpleExoPlayer extends BasePlayer private boolean handleAudioBecomingNoisy; private boolean skipSilenceEnabled; @C.VideoScalingMode private int videoScalingMode; + @C.VideoChangeFrameRateStrategy private int videoChangeFrameRateStrategy; private boolean useLazyPreparation; private SeekParameters seekParameters; private long seekBackIncrementMs; @@ -168,6 +170,8 @@ public class SimpleExoPlayer extends BasePlayer *
Note that the scaling mode only applies if a {@link MediaCodec}-based video {@link - * Renderer} is enabled and if the output surface is owned by a {@link - * android.view.SurfaceView}. + *
The scaling mode only applies if a {@link MediaCodec}-based video {@link Renderer} is + * enabled and if the output surface is owned by a {@link SurfaceView}. * * @param videoScalingMode A {@link C.VideoScalingMode}. * @return This builder. @@ -476,6 +480,27 @@ public class SimpleExoPlayer extends BasePlayer return this; } + /** + * Sets a {@link C.VideoChangeFrameRateStrategy} that will be used by the player when provided + * with a video output {@link Surface}. + * + *
The strategy only applies if a {@link MediaCodec}-based video {@link Renderer} is enabled. + * Applications wishing to use {@link Surface#CHANGE_FRAME_RATE_ALWAYS} should set the mode to + * {@link C#VIDEO_CHANGE_FRAME_RATE_STRATEGY_OFF} to disable calls to {@link + * Surface#setFrameRate} from ExoPlayer, and should then call {@link Surface#setFrameRate} + * directly from application code. + * + * @param videoChangeFrameRateStrategy A {@link C.VideoChangeFrameRateStrategy}. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + public Builder setVideoChangeFrameRateStrategy( + @C.VideoChangeFrameRateStrategy int videoChangeFrameRateStrategy) { + Assertions.checkState(!buildCalled); + this.videoChangeFrameRateStrategy = videoChangeFrameRateStrategy; + return this; + } + /** * Sets whether media sources should be initialized lazily. * @@ -661,6 +686,7 @@ public class SimpleExoPlayer extends BasePlayer private boolean surfaceHolderSurfaceIsVideoOutput; @Nullable private TextureView textureView; @C.VideoScalingMode private int videoScalingMode; + @C.VideoChangeFrameRateStrategy private int videoChangeFrameRateStrategy; private int surfaceWidth; private int surfaceHeight; @Nullable private DecoderCounters videoDecoderCounters; @@ -714,6 +740,7 @@ public class SimpleExoPlayer extends BasePlayer priorityTaskManager = builder.priorityTaskManager; audioAttributes = builder.audioAttributes; videoScalingMode = builder.videoScalingMode; + videoChangeFrameRateStrategy = builder.videoChangeFrameRateStrategy; skipSilenceEnabled = builder.skipSilenceEnabled; detachSurfaceTimeoutMs = builder.detachSurfaceTimeoutMs; componentListener = new ComponentListener(); @@ -799,6 +826,8 @@ public class SimpleExoPlayer extends BasePlayer sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, audioAttributes); sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode); + sendRendererMessage( + TRACK_TYPE_VIDEO, MSG_SET_CHANGE_FRAME_RATE_STRATEGY, videoChangeFrameRateStrategy); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_SKIP_SILENCE_ENABLED, skipSilenceEnabled); sendRendererMessage( TRACK_TYPE_VIDEO, MSG_SET_VIDEO_FRAME_METADATA_LISTENER, frameMetadataListener); @@ -851,14 +880,6 @@ public class SimpleExoPlayer extends BasePlayer return this; } - /** - * Sets the video scaling mode. - * - *
Note that the scaling mode only applies if a {@link MediaCodec}-based video {@link Renderer} - * is enabled and if the output surface is owned by a {@link android.view.SurfaceView}. - * - * @param videoScalingMode The {@link C.VideoScalingMode}. - */ @Override public void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode) { verifyApplicationThread(); @@ -872,6 +893,24 @@ public class SimpleExoPlayer extends BasePlayer return videoScalingMode; } + @Override + public void setVideoChangeFrameRateStrategy( + @C.VideoChangeFrameRateStrategy int videoChangeFrameRateStrategy) { + verifyApplicationThread(); + if (this.videoChangeFrameRateStrategy == videoChangeFrameRateStrategy) { + return; + } + this.videoChangeFrameRateStrategy = videoChangeFrameRateStrategy; + sendRendererMessage( + TRACK_TYPE_VIDEO, MSG_SET_CHANGE_FRAME_RATE_STRATEGY, videoChangeFrameRateStrategy); + } + + @Override + @C.VideoChangeFrameRateStrategy + public int getVideoChangeFrameRateStrategy() { + return videoChangeFrameRateStrategy; + } + @Override public VideoSize getVideoSize() { return videoSize; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index e1eb1da0ba..e5e1af9408 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -83,6 +83,8 @@ import java.util.List; * payload should be one of the integer scaling modes in {@link C.VideoScalingMode}. Note that * the scaling mode only applies if the {@link Surface} targeted by this renderer is owned by * a {@link android.view.SurfaceView}. + *
Does nothing if {@link #changeFrameRateStrategy} is {@link + * C#VIDEO_CHANGE_FRAME_RATE_STRATEGY_OFF}. + * + * @param forceUpdate Whether to call {@link Surface#setFrameRate} even if the frame rate is + * unchanged. */ - private void updateSurfacePlaybackFrameRate(boolean isNewSurface) { - if (Util.SDK_INT < 30 || surface == null) { + private void updateSurfacePlaybackFrameRate(boolean forceUpdate) { + if (Util.SDK_INT < 30 + || surface == null + || changeFrameRateStrategy == C.VIDEO_CHANGE_FRAME_RATE_STRATEGY_OFF) { return; } @@ -343,16 +364,24 @@ public final class VideoFrameReleaseHelper { } // We always set the frame-rate if we have a new surface, since we have no way of knowing what // it might have been set to previously. - if (!isNewSurface && this.surfacePlaybackFrameRate == surfacePlaybackFrameRate) { + if (!forceUpdate && this.surfacePlaybackFrameRate == surfacePlaybackFrameRate) { return; } this.surfacePlaybackFrameRate = surfacePlaybackFrameRate; Api30.setSurfaceFrameRate(surface, surfacePlaybackFrameRate); } - /** Clears the frame-rate of the current {@link #surface}. */ + /** + * Clears the frame-rate of the current {@link #surface}. + * + *
Does nothing if {@link #changeFrameRateStrategy} is {@link + * C#VIDEO_CHANGE_FRAME_RATE_STRATEGY_OFF}. + */ private void clearSurfaceFrameRate() { - if (Util.SDK_INT < 30 || surface == null || surfacePlaybackFrameRate == 0) { + if (Util.SDK_INT < 30 + || surface == null + || changeFrameRateStrategy == C.VIDEO_CHANGE_FRAME_RATE_STRATEGY_OFF + || surfacePlaybackFrameRate == 0) { return; } surfacePlaybackFrameRate = 0;