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