From 0f7ef1ea60c7e35fd78ede7468911791091ff088 Mon Sep 17 00:00:00 2001 From: krocard Date: Thu, 29 Apr 2021 11:35:29 +0100 Subject: [PATCH] Add get video size Move VideoSize in the common module and have the Player return it. `Listener` and `AnalyticsListener` `onVideoSizeChanged` are updated with the old method deprecated. `VideoRendererEventListener.onVideoSizeChanged` was also migrated to `VideoSize` but the old method is removed, not deprecated. This is because: - apps calling/listening to this method is a rare and niche use-case. - it would introduce hard to diagnostic issues where if only the caller or the callee is updated to use the new method, the event will be lost. This doesn't occur with the other 2 listeners as the caller is always in ExoPlayer library and was updated to call both the old and new methods. VideoSize is used everywhere except in `Format` as this would lead to too much refactoring and backward compatibility breakage for little gain. #minor-release PiperOrigin-RevId: 371087419 --- RELEASENOTES.md | 4 + .../exoplayer2/ext/cast/CastPlayer.java | 7 + .../android/exoplayer2/ForwardingPlayer.java | 6 + .../com/google/android/exoplayer2/Player.java | 11 ++ .../exoplayer2/video/VideoListener.java | 17 +- .../android/exoplayer2/video/VideoSize.java | 171 ++++++++++++++++++ .../exoplayer2/video/VideoSizeTest.java | 53 ++++++ .../google/android/exoplayer2/ExoPlayer.java | 11 ++ .../android/exoplayer2/ExoPlayerImpl.java | 7 + .../android/exoplayer2/SimpleExoPlayer.java | 21 ++- .../analytics/AnalyticsCollector.java | 17 +- .../analytics/AnalyticsListener.java | 13 +- .../analytics/PlaybackStatsListener.java | 28 +-- .../android/exoplayer2/util/EventLogger.java | 10 +- .../video/DecoderVideoRenderer.java | 24 +-- .../video/MediaCodecVideoRenderer.java | 36 ++-- .../video/VideoRendererEventListener.java | 31 +--- .../analytics/AnalyticsCollectorTest.java | 10 +- .../video/MediaCodecVideoRendererTest.java | 26 +-- .../testutil/FakeVideoRenderer.java | 4 +- .../exoplayer2/testutil/StubExoPlayer.java | 6 + 21 files changed, 383 insertions(+), 130 deletions(-) create mode 100644 library/common/src/main/java/com/google/android/exoplayer2/video/VideoSize.java create mode 100644 library/common/src/test/java/com/google/android/exoplayer2/video/VideoSizeTest.java diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d4f0444bcc..56e85fb500 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -61,6 +61,10 @@ * Allow forcing offload for gapless content even if gapless playback is not supported. * Allow fall back from DTS-HD to DTS when playing via passthrough. +* Video: + * Add `Player.getVideoSize()` to retrieve the current size of the video + stream. Add `Listener.onVideoSizeChanged(VideoSize)` and + deprecate `Listener.onVideoSizeChanged(int weight, int height...)`. * Analytics: * Add `onAudioCodecError` and `onVideoCodecError` to `AnalyticsListener`. * Downloads and caching: diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index a1cf436a4a..877cea386d 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -48,6 +48,7 @@ import com.google.android.exoplayer2.util.ListenerSet; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.VideoSize; import com.google.android.gms.cast.CastStatusCodes; import com.google.android.gms.cast.MediaInfo; import com.google.android.gms.cast.MediaQueueItem; @@ -686,6 +687,12 @@ public final class CastPlayer extends BasePlayer { @Override public void clearVideoTextureView(@Nullable TextureView textureView) {} + /** This method is not supported and returns {@link VideoSize#UNKNOWN}. */ + @Override + public VideoSize getVideoSize() { + return VideoSize.UNKNOWN; + } + /** This method is not supported and returns an empty list. */ @Override public ImmutableList getCurrentCues() { diff --git a/library/common/src/main/java/com/google/android/exoplayer2/ForwardingPlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/ForwardingPlayer.java index e871c9bdc9..e7aaafd689 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/ForwardingPlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/ForwardingPlayer.java @@ -27,6 +27,7 @@ import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.video.VideoSize; import java.util.List; /** @@ -274,6 +275,11 @@ public class ForwardingPlayer extends BasePlayer { return player.getVolume(); } + @Override + public VideoSize getVideoSize() { + return player.getVideoSize(); + } + @Override public void clearVideoSurface() { player.clearVideoSurface(); diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Player.java b/library/common/src/main/java/com/google/android/exoplayer2/Player.java index 4e3348fb8a..398d6a4dee 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Player.java @@ -36,6 +36,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.util.ExoFlags; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoListener; +import com.google.android.exoplayer2.video.VideoSize; import com.google.common.base.Objects; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -1797,6 +1798,16 @@ public interface Player { */ void clearVideoTextureView(@Nullable TextureView textureView); + /** + * Gets the size of the video. + * + *

The video's width and height are {@code 0} if there is no video or its size has not been + * determined yet. + * + * @see Listener#onVideoSizeChanged(int, int, int, float) + */ + VideoSize getVideoSize(); + /** Returns the current {@link Cue Cues}. This list may be empty. */ List getCurrentCues(); diff --git a/library/common/src/main/java/com/google/android/exoplayer2/video/VideoListener.java b/library/common/src/main/java/com/google/android/exoplayer2/video/VideoListener.java index eb013ed425..63e150b9ae 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/video/VideoListener.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/video/VideoListener.java @@ -23,19 +23,12 @@ public interface VideoListener { /** * Called each time there's a change in the size of the video being rendered. * - * @param width The video width in pixels. - * @param height The video height in pixels. - * @param unappliedRotationDegrees For videos that require a rotation, this is the clockwise - * rotation in degrees that the application should apply for the video for it to be rendered - * in the correct orientation. This value will always be zero on API levels 21 and above, - * since the renderer will apply all necessary rotations internally. On earlier API levels - * this is not possible. Applications that use {@link android.view.TextureView} can apply the - * rotation by calling {@link android.view.TextureView#setTransform}. Applications that do not - * expect to encounter rotated videos can safely ignore this parameter. - * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of - * square pixels this will be equal to 1.0. Different values are indicative of anamorphic - * content. + * @param videoSize The new size of the video. */ + default void onVideoSizeChanged(VideoSize videoSize) {} + + /** @deprecated Use {@link #onVideoSizeChanged(VideoSize videoSize)}. */ + @Deprecated default void onVideoSizeChanged( int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {} diff --git a/library/common/src/main/java/com/google/android/exoplayer2/video/VideoSize.java b/library/common/src/main/java/com/google/android/exoplayer2/video/VideoSize.java new file mode 100644 index 0000000000..d6e16a44ae --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/video/VideoSize.java @@ -0,0 +1,171 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.video; + +import android.os.Bundle; +import androidx.annotation.FloatRange; +import androidx.annotation.IntDef; +import androidx.annotation.IntRange; +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.Bundleable; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** Represents the video size. */ +public final class VideoSize implements Bundleable { + + private static final int DEFAULT_WIDTH = 0; + private static final int DEFAULT_HEIGHT = 0; + private static final int DEFAULT_UNAPPLIED_ROTATION_DEGREES = 0; + private static final float DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO = 1F; + + public static final VideoSize UNKNOWN = new VideoSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); + + /** The video width in pixels, 0 when unknown. */ + @IntRange(from = 0) + public final int width; + + /** The video height in pixels, 0 when unknown. */ + @IntRange(from = 0) + public final int height; + + /** + * Clockwise rotation in degrees that the application should apply for the video for it to be + * rendered in the correct orientation. + * + *

Is 0 if unknown or if no rotation is needed. + * + *

Player should apply video rotation internally, in which case unappliedRotationDegrees is 0. + * But when a player can't apply the rotation, for example before API level 21, the unapplied + * rotation is reported by this field for application to handle. + * + *

Applications that use {@link android.view.TextureView} can apply the rotation by calling + * {@link android.view.TextureView#setTransform}. + */ + @IntRange(from = 0, to = 359) + public final int unappliedRotationDegrees; + + /** + * The width to height ratio of each pixel, 1 if unknown. + * + *

For the normal case of square pixels this will be equal to 1.0. Different values are + * indicative of anamorphic content. + */ + @FloatRange(from = 0, fromInclusive = false) + public final float pixelWidthHeightRatio; + + /** + * Creates a VideoSize without unapplied rotation or anamorphic content. + * + * @param width The video width in pixels. + * @param height The video height in pixels. + */ + public VideoSize(@IntRange(from = 0) int width, @IntRange(from = 0) int height) { + this(width, height, DEFAULT_UNAPPLIED_ROTATION_DEGREES, DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO); + } + + /** + * Creates a VideoSize. + * + * @param width The video width in pixels. + * @param height The video height in pixels. + * @param unappliedRotationDegrees Clockwise rotation in degrees that the application should apply + * for the video for it to be rendered in the correct orientation. See {@link + * #unappliedRotationDegrees}. + * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of + * square pixels this will be equal to 1.0. Different values are indicative of anamorphic + * content. + */ + public VideoSize( + @IntRange(from = 0) int width, + @IntRange(from = 0) int height, + @IntRange(from = 0, to = 359) int unappliedRotationDegrees, + @FloatRange(from = 0, fromInclusive = false) float pixelWidthHeightRatio) { + this.width = width; + this.height = height; + this.unappliedRotationDegrees = unappliedRotationDegrees; + this.pixelWidthHeightRatio = pixelWidthHeightRatio; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof VideoSize) { + VideoSize other = (VideoSize) obj; + return width == other.width + && height == other.height + && unappliedRotationDegrees == other.unappliedRotationDegrees + && pixelWidthHeightRatio == other.pixelWidthHeightRatio; + } + return false; + } + + @Override + public int hashCode() { + int result = 7; + result = 31 * result + width; + result = 31 * result + height; + result = 31 * result + unappliedRotationDegrees; + result = 31 * result + Float.floatToRawIntBits(pixelWidthHeightRatio); + return result; + } + + // Bundleable implementation. + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_WIDTH, + FIELD_HEIGHT, + FIELD_UNAPPLIED_ROTATION_DEGREES, + FIELD_PIXEL_WIDTH_HEIGHT_RATIO, + }) + private @interface FieldNumber {} + + private static final int FIELD_WIDTH = 0; + private static final int FIELD_HEIGHT = 1; + private static final int FIELD_UNAPPLIED_ROTATION_DEGREES = 2; + private static final int FIELD_PIXEL_WIDTH_HEIGHT_RATIO = 3; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putInt(keyForField(FIELD_WIDTH), width); + bundle.putInt(keyForField(FIELD_HEIGHT), height); + bundle.putInt(keyForField(FIELD_UNAPPLIED_ROTATION_DEGREES), unappliedRotationDegrees); + bundle.putFloat(keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), pixelWidthHeightRatio); + return bundle; + } + + public static final Creator CREATOR = + bundle -> { + int width = bundle.getInt(keyForField(FIELD_WIDTH), DEFAULT_WIDTH); + int height = bundle.getInt(keyForField(FIELD_HEIGHT), DEFAULT_HEIGHT); + int unappliedRotationDegrees = + bundle.getInt( + keyForField(FIELD_UNAPPLIED_ROTATION_DEGREES), DEFAULT_UNAPPLIED_ROTATION_DEGREES); + float pixelWidthHeightRatio = + bundle.getFloat( + keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO); + return new VideoSize(width, height, unappliedRotationDegrees, pixelWidthHeightRatio); + }; + + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } +} diff --git a/library/common/src/test/java/com/google/android/exoplayer2/video/VideoSizeTest.java b/library/common/src/test/java/com/google/android/exoplayer2/video/VideoSizeTest.java new file mode 100644 index 0000000000..ff9f7c5758 --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/video/VideoSizeTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.video; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Bundle; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit test for {@link VideoSize}. */ +@RunWith(AndroidJUnit4.class) +public final class VideoSizeTest { + + @Test + public void roundTripViaBundle_ofVideoSizeUnknown_yieldsEqualInstance() { + assertThat(roundTripViaBundle(VideoSize.UNKNOWN)).isEqualTo(VideoSize.UNKNOWN); + } + + @Test + public void roundTripViaBundle_ofArbitraryVideoSize_yieldsEqualInstance() { + VideoSize videoSize = + new VideoSize( + /* width= */ 9, + /* height= */ 8, + /* unappliedRotationDegrees= */ 7, + /* pixelWidthHeightRatio= */ 6); + assertThat(roundTripViaBundle(videoSize)).isEqualTo(videoSize); + } + + @Test + public void fromBundle_ofEmptyBundle_yieldsVideoSizeUnknown() { + assertThat(VideoSize.CREATOR.fromBundle(new Bundle())).isEqualTo(VideoSize.UNKNOWN); + } + + private static VideoSize roundTripViaBundle(VideoSize videoSize) { + return VideoSize.CREATOR.fromBundle(videoSize.toBundle()); + } +} 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 11bef0a035..0b8c04b036 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 @@ -54,6 +54,7 @@ import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoListener; +import com.google.android.exoplayer2.video.VideoSize; import com.google.android.exoplayer2.video.spherical.CameraMotionListener; import java.util.List; @@ -368,6 +369,16 @@ public interface ExoPlayer extends Player { * @param textureView The texture view to clear. */ void clearVideoTextureView(@Nullable TextureView textureView); + + /** + * Gets the size of the video. + * + *

The width and height of size could be 0 if there is no video or the size has not been + * determined yet. + * + * @see Listener#onVideoSizeChanged(int, int, int, float) + */ + VideoSize getVideoSize(); } /** The text component of an {@link ExoPlayer}. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index c6f8e18ae4..8ba1ff5fba 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -52,6 +52,7 @@ import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.android.exoplayer2.util.ListenerSet; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.VideoSize; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Collections; @@ -1056,6 +1057,12 @@ import java.util.concurrent.CopyOnWriteArraySet; @Override public void clearVideoTextureView(@Nullable TextureView textureView) {} + /** This method is not supported and returns {@link VideoSize#UNKNOWN}. */ + @Override + public VideoSize getVideoSize() { + return VideoSize.UNKNOWN; + } + /** This method is not supported and returns an empty list. */ @Override public ImmutableList getCurrentCues() { 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 e70497dcd1..92afdf1074 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 @@ -76,6 +76,7 @@ import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoListener; import com.google.android.exoplayer2.video.VideoRendererEventListener; +import com.google.android.exoplayer2.video.VideoSize; import com.google.android.exoplayer2.video.spherical.CameraMotionListener; import com.google.android.exoplayer2.video.spherical.SphericalGLSurfaceView; import java.util.ArrayList; @@ -636,6 +637,7 @@ public class SimpleExoPlayer extends BasePlayer private boolean isPriorityTaskManagerRegistered; private boolean playerReleased; private DeviceInfo deviceInfo; + private VideoSize videoSize; /** @deprecated Use the {@link Builder} and pass it to {@link #SimpleExoPlayer(Builder)}. */ @Deprecated @@ -748,6 +750,7 @@ public class SimpleExoPlayer extends BasePlayer wifiLockManager = new WifiLockManager(builder.context); wifiLockManager.setEnabled(builder.wakeMode == C.WAKE_MODE_NETWORK); deviceInfo = createDeviceInfo(streamVolumeManager); + videoSize = VideoSize.UNKNOWN; sendRendererMessage(C.TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); sendRendererMessage(C.TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); @@ -826,6 +829,11 @@ public class SimpleExoPlayer extends BasePlayer return videoScalingMode; } + @Override + public VideoSize getVideoSize() { + return videoSize; + } + @Override public void clearVideoSurface() { verifyApplicationThread(); @@ -2122,13 +2130,16 @@ public class SimpleExoPlayer extends BasePlayer } @Override - public void onVideoSizeChanged( - int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { - analyticsCollector.onVideoSizeChanged( - width, height, unappliedRotationDegrees, pixelWidthHeightRatio); + public void onVideoSizeChanged(VideoSize videoSize) { + SimpleExoPlayer.this.videoSize = videoSize; + analyticsCollector.onVideoSizeChanged(videoSize); for (VideoListener videoListener : videoListeners) { + videoListener.onVideoSizeChanged(videoSize); videoListener.onVideoSizeChanged( - width, height, unappliedRotationDegrees, pixelWidthHeightRatio); + videoSize.width, + videoSize.height, + videoSize.unappliedRotationDegrees, + videoSize.pixelWidthHeightRatio); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java index 8b47d629e1..e72f5f1056 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java @@ -52,6 +52,7 @@ import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.ListenerSet; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoRendererEventListener; +import com.google.android.exoplayer2.video.VideoSize; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -430,16 +431,22 @@ public class AnalyticsCollector }); } + @SuppressWarnings("deprecation") // Calling deprecated listener method. @Override - public final void onVideoSizeChanged( - int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { + public final void onVideoSizeChanged(VideoSize videoSize) { EventTime eventTime = generateReadingMediaPeriodEventTime(); sendEvent( eventTime, AnalyticsListener.EVENT_VIDEO_SIZE_CHANGED, - listener -> - listener.onVideoSizeChanged( - eventTime, width, height, unappliedRotationDegrees, pixelWidthHeightRatio)); + listener -> { + listener.onVideoSizeChanged(eventTime, videoSize); + listener.onVideoSizeChanged( + eventTime, + videoSize.width, + videoSize.height, + videoSize.unappliedRotationDegrees, + videoSize.pixelWidthHeightRatio); + }); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java index 3d9d5bbf80..b345be32e3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java @@ -51,6 +51,7 @@ import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.util.ExoFlags; import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer; +import com.google.android.exoplayer2.video.VideoSize; import com.google.common.base.Objects; import java.io.IOException; import java.lang.annotation.Documented; @@ -1050,14 +1051,12 @@ public interface AnalyticsListener { * there's a change in the size or pixel aspect ratio of the video being rendered. * * @param eventTime The event time. - * @param width The width of the video. - * @param height The height of the video. - * @param unappliedRotationDegrees For videos that require a rotation, this is the clockwise - * rotation in degrees that the application should apply for the video for it to be rendered - * in the correct orientation. This value will always be zero on API levels 21 and above, - * since the renderer will apply all necessary rotations internally. - * @param pixelWidthHeightRatio The width to height ratio of each pixel. + * @param videoSize The new size of the video. */ + default void onVideoSizeChanged(EventTime eventTime, VideoSize videoSize) {} + + /** @deprecated Implement {@link #onVideoSizeChanged(EventTime eventTime, VideoSize)} instead. */ + @Deprecated default void onVideoSizeChanged( EventTime eventTime, int width, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java index 9465bd2387..2ca886a5e6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java @@ -38,6 +38,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.VideoSize; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -89,8 +90,7 @@ public final class PlaybackStatsListener private long bandwidthBytes; @Nullable private Format videoFormat; @Nullable private Format audioFormat; - private int videoHeight; - private int videoWidth; + private VideoSize videoSize; /** * Creates listener for playback stats. @@ -107,6 +107,7 @@ public final class PlaybackStatsListener sessionStartEventTimes = new HashMap<>(); finishedPlaybackStats = PlaybackStats.EMPTY; period = new Period(); + videoSize = VideoSize.UNKNOWN; sessionManager.setListener(this); } @@ -229,10 +230,8 @@ public final class PlaybackStatsListener } @Override - public void onVideoSizeChanged( - EventTime eventTime, int width, int height, int rotationDegrees, float pixelRatio) { - videoWidth = width; - videoHeight = height; + public void onVideoSizeChanged(EventTime eventTime, VideoSize videoSize) { + this.videoSize = videoSize; } @Override @@ -270,8 +269,7 @@ public final class PlaybackStatsListener hasBandwidthData ? bandwidthBytes : 0, hasFormatData ? videoFormat : null, hasFormatData ? audioFormat : null, - hasVideoSize ? videoHeight : Format.NO_VALUE, - hasVideoSize ? videoWidth : Format.NO_VALUE); + hasVideoSize ? videoSize : null); } videoFormat = null; audioFormat = null; @@ -480,8 +478,7 @@ public final class PlaybackStatsListener * @param bandwidthBytes The number of bytes loaded for this playback. * @param videoFormat A reported downstream video format for this playback, or null. * @param audioFormat A reported downstream audio format for this playback, or null. - * @param videoHeight The reported video height for this playback, or {@link Format#NO_VALUE}. - * @param videoWidth The reported video width for this playback, or {@link Format#NO_VALUE}. + * @param videoSize The reported video size for this playback, or null. */ public void onEvents( Player player, @@ -498,8 +495,7 @@ public final class PlaybackStatsListener long bandwidthBytes, @Nullable Format videoFormat, @Nullable Format audioFormat, - int videoHeight, - int videoWidth) { + @Nullable VideoSize videoSize) { if (discontinuityFromPositionMs != C.TIME_UNSET) { maybeUpdateMediaTimeHistory(eventTime.realtimeMs, discontinuityFromPositionMs); isSeeking = true; @@ -550,9 +546,13 @@ public final class PlaybackStatsListener } if (currentVideoFormat != null && currentVideoFormat.height == Format.NO_VALUE - && videoHeight != Format.NO_VALUE) { + && videoSize != null) { Format formatWithHeightAndWidth = - currentVideoFormat.buildUpon().setWidth(videoWidth).setHeight(videoHeight).build(); + currentVideoFormat + .buildUpon() + .setWidth(videoSize.width) + .setHeight(videoSize.height) + .build(); maybeUpdateVideoFormat(eventTime, formatWithHeightAndWidth); } if (startedLoading) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java index ceec6e82cd..12513f597a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java @@ -44,6 +44,7 @@ import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.video.VideoSize; import java.io.IOException; import java.text.NumberFormat; import java.util.List; @@ -457,13 +458,8 @@ public class EventLogger implements AnalyticsListener { } @Override - public void onVideoSizeChanged( - EventTime eventTime, - int width, - int height, - int unappliedRotationDegrees, - float pixelWidthHeightRatio) { - logd(eventTime, "videoSize", width + ", " + height); + public void onVideoSizeChanged(EventTime eventTime, VideoSize videoSize) { + logd(eventTime, "videoSize", videoSize.width + ", " + videoSize.height); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java index b86e4ff4d8..d351442192 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java @@ -133,8 +133,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { private boolean inputStreamEnded; private boolean outputStreamEnded; - private int reportedWidth; - private int reportedHeight; + @Nullable private VideoSize reportedVideoSize; private long droppedFrameAccumulationStartTimeMs; private int droppedFrames; @@ -914,26 +913,21 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { } private void clearReportedVideoSize() { - reportedWidth = Format.NO_VALUE; - reportedHeight = Format.NO_VALUE; + reportedVideoSize = null; } private void maybeNotifyVideoSizeChanged(int width, int height) { - if (reportedWidth != width || reportedHeight != height) { - reportedWidth = width; - reportedHeight = height; - eventDispatcher.videoSizeChanged( - width, height, /* unappliedRotationDegrees= */ 0, /* pixelWidthHeightRatio= */ 1); + if (reportedVideoSize == null + || reportedVideoSize.width != width + || reportedVideoSize.height != height) { + reportedVideoSize = new VideoSize(width, height); + eventDispatcher.videoSizeChanged(reportedVideoSize); } } private void maybeRenotifyVideoSizeChanged() { - if (reportedWidth != Format.NO_VALUE || reportedHeight != Format.NO_VALUE) { - eventDispatcher.videoSizeChanged( - reportedWidth, - reportedHeight, - /* unappliedRotationDegrees= */ 0, - /* pixelWidthHeightRatio= */ 1); + if (reportedVideoSize != null) { + eventDispatcher.videoSizeChanged(reportedVideoSize); } } 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 26492bdf82..09bb6fb570 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 @@ -145,10 +145,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { private int currentHeight; private int currentUnappliedRotationDegrees; private float currentPixelWidthHeightRatio; - private int reportedWidth; - private int reportedHeight; - private int reportedUnappliedRotationDegrees; - private float reportedPixelWidthHeightRatio; + @Nullable private VideoSize reportedVideoSize; private boolean tunneling; private int tunnelingAudioSessionId; @@ -1200,30 +1197,29 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } private void clearReportedVideoSize() { - reportedWidth = Format.NO_VALUE; - reportedHeight = Format.NO_VALUE; - reportedPixelWidthHeightRatio = Format.NO_VALUE; - reportedUnappliedRotationDegrees = Format.NO_VALUE; + reportedVideoSize = null; } private void maybeNotifyVideoSizeChanged() { if ((currentWidth != Format.NO_VALUE || currentHeight != Format.NO_VALUE) - && (reportedWidth != currentWidth || reportedHeight != currentHeight - || reportedUnappliedRotationDegrees != currentUnappliedRotationDegrees - || reportedPixelWidthHeightRatio != currentPixelWidthHeightRatio)) { - eventDispatcher.videoSizeChanged(currentWidth, currentHeight, currentUnappliedRotationDegrees, - currentPixelWidthHeightRatio); - reportedWidth = currentWidth; - reportedHeight = currentHeight; - reportedUnappliedRotationDegrees = currentUnappliedRotationDegrees; - reportedPixelWidthHeightRatio = currentPixelWidthHeightRatio; + && (reportedVideoSize == null + || reportedVideoSize.width != currentWidth + || reportedVideoSize.height != currentHeight + || reportedVideoSize.unappliedRotationDegrees != currentUnappliedRotationDegrees + || reportedVideoSize.pixelWidthHeightRatio != currentPixelWidthHeightRatio)) { + reportedVideoSize = + new VideoSize( + currentWidth, + currentHeight, + currentUnappliedRotationDegrees, + currentPixelWidthHeightRatio); + eventDispatcher.videoSizeChanged(reportedVideoSize); } } private void maybeRenotifyVideoSizeChanged() { - if (reportedWidth != Format.NO_VALUE || reportedHeight != Format.NO_VALUE) { - eventDispatcher.videoSizeChanged(reportedWidth, reportedHeight, - reportedUnappliedRotationDegrees, reportedPixelWidthHeightRatio); + if (reportedVideoSize != null) { + eventDispatcher.videoSizeChanged(reportedVideoSize); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java index ae84ad15fa..494620f89a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java @@ -22,7 +22,6 @@ import android.media.MediaCodec.CodecException; import android.os.Handler; import android.os.SystemClock; import android.view.Surface; -import android.view.TextureView; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Player; @@ -108,21 +107,9 @@ public interface VideoRendererEventListener { * Called before a frame is rendered for the first time since setting the surface, and each time * there's a change in the size, rotation or pixel aspect ratio of the video being rendered. * - * @param width The video width in pixels. - * @param height The video height in pixels. - * @param unappliedRotationDegrees For videos that require a rotation, this is the clockwise - * rotation in degrees that the application should apply for the video for it to be rendered - * in the correct orientation. This value will always be zero on API levels 21 and above, - * since the renderer will apply all necessary rotations internally. On earlier API levels - * this is not possible. Applications that use {@link TextureView} can apply the rotation by - * calling {@link TextureView#setTransform}. Applications that do not expect to encounter - * rotated videos can safely ignore this parameter. - * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of - * square pixels this will be equal to 1.0. Different values are indicative of anamorphic - * content. + * @param videoSize The new size of the video. */ - default void onVideoSizeChanged( - int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {} + default void onVideoSizeChanged(VideoSize videoSize) {} /** * Called when a frame is rendered for the first time since setting the output, or since the @@ -232,18 +219,10 @@ public interface VideoRendererEventListener { } } - /** Invokes {@link VideoRendererEventListener#onVideoSizeChanged(int, int, int, float)}. */ - public void videoSizeChanged( - int width, - int height, - final int unappliedRotationDegrees, - final float pixelWidthHeightRatio) { + /** Invokes {@link VideoRendererEventListener#onVideoSizeChanged(VideoSize)}. */ + public void videoSizeChanged(VideoSize videoSize) { if (handler != null) { - handler.post( - () -> - castNonNull(listener) - .onVideoSizeChanged( - width, height, unappliedRotationDegrees, pixelWidthHeightRatio)); + handler.post(() -> castNonNull(listener).onVideoSizeChanged(videoSize)); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java index bfe1f90a8a..48aa8b7a93 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java @@ -113,6 +113,7 @@ import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.ConditionVariable; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.VideoSize; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.util.ArrayList; @@ -1769,6 +1770,8 @@ public final class AnalyticsCollectorTest { .onRenderedFirstFrame(individualRenderedFirstFrameEventTimes.capture(), any(), anyLong()); ArgumentCaptor individualVideoSizeChangedEventTimes = ArgumentCaptor.forClass(AnalyticsListener.EventTime.class); + verify(listener, atLeastOnce()) + .onVideoSizeChanged(individualVideoSizeChangedEventTimes.capture(), any()); verify(listener, atLeastOnce()) .onVideoSizeChanged( individualVideoSizeChangedEventTimes.capture(), @@ -2314,12 +2317,7 @@ public final class AnalyticsCollectorTest { } @Override - public void onVideoSizeChanged( - EventTime eventTime, - int width, - int height, - int unappliedRotationDegrees, - float pixelWidthHeightRatio) { + public void onVideoSizeChanged(EventTime eventTime, VideoSize videoSize) { reportedEvents.add(new ReportedEvent(EVENT_VIDEO_SIZE_CHANGED, eventTime)); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java index 36f86dcde9..58b9424bb6 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java @@ -19,10 +19,8 @@ import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSample import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.format; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -51,12 +49,13 @@ import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; import java.util.Collections; +import java.util.stream.Collectors; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.InOrder; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -200,10 +199,11 @@ public class MediaCodecVideoRendererTest { verify(eventListener) .onVideoSizeChanged( - VIDEO_H264.width, - VIDEO_H264.height, - VIDEO_H264.rotationDegrees, - VIDEO_H264.pixelWidthHeightRatio); + new VideoSize( + VIDEO_H264.width, + VIDEO_H264.height, + VIDEO_H264.rotationDegrees, + VIDEO_H264.pixelWidthHeightRatio)); } @Test @@ -256,11 +256,13 @@ public class MediaCodecVideoRendererTest { } while (!mediaCodecVideoRenderer.isEnded()); shadowOf(testMainLooper).idle(); - InOrder orderVerifier = inOrder(eventListener); - orderVerifier.verify(eventListener).onVideoSizeChanged(anyInt(), anyInt(), anyInt(), eq(1f)); - orderVerifier.verify(eventListener).onVideoSizeChanged(anyInt(), anyInt(), anyInt(), eq(2f)); - orderVerifier.verify(eventListener).onVideoSizeChanged(anyInt(), anyInt(), anyInt(), eq(3f)); - orderVerifier.verifyNoMoreInteractions(); + ArgumentCaptor videoSizesCaptor = ArgumentCaptor.forClass(VideoSize.class); + verify(eventListener, times(3)).onVideoSizeChanged(videoSizesCaptor.capture()); + assertThat( + videoSizesCaptor.getAllValues().stream() + .map(videoSize -> videoSize.pixelWidthHeightRatio) + .collect(Collectors.toList())) + .containsExactly(1f, 2f, 3f); } @Test diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeVideoRenderer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeVideoRenderer.java index 3c87d98a63..6d328b5875 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeVideoRenderer.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeVideoRenderer.java @@ -26,6 +26,7 @@ import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.video.VideoRendererEventListener; +import com.google.android.exoplayer2.video.VideoSize; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** A {@link FakeRenderer} that supports {@link C#TRACK_TYPE_VIDEO}. */ @@ -119,7 +120,8 @@ public class FakeVideoRenderer extends FakeRenderer { if (shouldProcess && !renderedFirstFrameAfterReset && output != null) { @MonotonicNonNull Format format = Assertions.checkNotNull(this.format); eventDispatcher.videoSizeChanged( - format.width, format.height, format.rotationDegrees, format.pixelWidthHeightRatio); + new VideoSize( + format.width, format.height, format.rotationDegrees, format.pixelWidthHeightRatio)); eventDispatcher.renderedFirstFrame(output); renderedFirstFrameAfterReset = true; renderedFirstFrameAfterEnable = true; diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index 930ff01bf4..c3498820dd 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -41,6 +41,7 @@ import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.util.Clock; +import com.google.android.exoplayer2.video.VideoSize; import java.util.List; /** @@ -489,6 +490,11 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer { throw new UnsupportedOperationException(); } + @Override + public VideoSize getVideoSize() { + throw new UnsupportedOperationException(); + } + @Override public List getCurrentCues() { throw new UnsupportedOperationException();