From b780635c0b5968c9d82f36e66cf4c07b6149aed8 Mon Sep 17 00:00:00 2001 From: Googler Date: Thu, 3 Nov 2022 15:50:19 +0000 Subject: [PATCH] Add 'Player.getVideoSurfaceSize' that returns the size of the surface on which the video is rendered. Design Doc: go/aaos-mu-media-dd PiperOrigin-RevId: 485884772 --- RELEASENOTES.md | 2 + .../java/androidx/media3/cast/CastPlayer.java | 7 ++ .../media3/common/ForwardingPlayer.java | 7 ++ .../java/androidx/media3/common/Player.java | 9 ++ .../media3/common/SimpleBasePlayer.java | 7 ++ .../androidx/media3/common/util/Size.java | 85 +++++++++++++++++++ .../media3/exoplayer/ExoPlayerImpl.java | 16 ++-- .../media3/exoplayer/SimpleExoPlayer.java | 7 ++ .../media3/session/MediaController.java | 10 +++ .../session/MediaControllerImplBase.java | 15 ++-- .../session/MediaControllerImplLegacy.java | 7 ++ .../androidx/media3/session/MockPlayer.java | 38 +++++++++ .../media3/test/utils/StubPlayer.java | 6 ++ 13 files changed, 206 insertions(+), 10 deletions(-) create mode 100644 libraries/common/src/main/java/androidx/media3/common/util/Size.java diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1f08de9bee..8924c6571d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -28,6 +28,8 @@ Release notes ([#10667](https://github.com/google/ExoPlayer/issues/10667)). * Enforce minimum `compileSdkVersion` to avoid compilation errors ([#10684](https://github.com/google/ExoPlayer/issues/10684)). + * Add `Player.getVideoSurfaceSize` that returns the size of the surface on + which the video is rendered. * Downloads: * Fix potential infinite loop in `ProgressiveDownloader` caused by simultaneous download and playback with the same `PriorityTaskManager` diff --git a/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java b/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java index 72682c7073..195f6c62ee 100644 --- a/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java +++ b/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java @@ -47,6 +47,7 @@ import androidx.media3.common.util.Assertions; import androidx.media3.common.util.Clock; import androidx.media3.common.util.ListenerSet; import androidx.media3.common.util.Log; +import androidx.media3.common.util.Size; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import com.google.android.gms.cast.CastStatusCodes; @@ -727,6 +728,12 @@ public final class CastPlayer extends BasePlayer { return VideoSize.UNKNOWN; } + /** This method is not supported and returns {@link Size#UNKNOWN}. */ + @Override + public Size getVideoSurfaceSize() { + return Size.UNKNOWN; + } + /** This method is not supported and returns an empty {@link CueGroup}. */ @Override public CueGroup getCurrentCues() { diff --git a/libraries/common/src/main/java/androidx/media3/common/ForwardingPlayer.java b/libraries/common/src/main/java/androidx/media3/common/ForwardingPlayer.java index 5975897b40..6afdb840b5 100644 --- a/libraries/common/src/main/java/androidx/media3/common/ForwardingPlayer.java +++ b/libraries/common/src/main/java/androidx/media3/common/ForwardingPlayer.java @@ -23,6 +23,7 @@ import android.view.TextureView; import androidx.annotation.Nullable; import androidx.media3.common.text.Cue; import androidx.media3.common.text.CueGroup; +import androidx.media3.common.util.Size; import androidx.media3.common.util.UnstableApi; import java.util.List; @@ -759,6 +760,12 @@ public class ForwardingPlayer implements Player { return player.getVideoSize(); } + /** Calls {@link Player#getVideoSurfaceSize()} on the delegate and returns the result. */ + @Override + public Size getVideoSurfaceSize() { + return player.getVideoSurfaceSize(); + } + /** Calls {@link Player#clearVideoSurface()} on the delegate. */ @Override public void clearVideoSurface() { diff --git a/libraries/common/src/main/java/androidx/media3/common/Player.java b/libraries/common/src/main/java/androidx/media3/common/Player.java index 65fbcda652..380bb8ce25 100644 --- a/libraries/common/src/main/java/androidx/media3/common/Player.java +++ b/libraries/common/src/main/java/androidx/media3/common/Player.java @@ -33,6 +33,7 @@ import androidx.annotation.IntRange; import androidx.annotation.Nullable; import androidx.media3.common.text.Cue; import androidx.media3.common.text.CueGroup; +import androidx.media3.common.util.Size; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import com.google.common.base.Objects; @@ -2498,6 +2499,14 @@ public interface Player { */ VideoSize getVideoSize(); + /** + * Gets the size of the surface on which the video is rendered. + * + * @see Listener#onSurfaceSizeChanged(int, int) + */ + @UnstableApi + Size getVideoSurfaceSize(); + /** Returns the current {@link CueGroup}. */ CueGroup getCurrentCues(); diff --git a/libraries/common/src/main/java/androidx/media3/common/SimpleBasePlayer.java b/libraries/common/src/main/java/androidx/media3/common/SimpleBasePlayer.java index 323c0e653c..c305e76809 100644 --- a/libraries/common/src/main/java/androidx/media3/common/SimpleBasePlayer.java +++ b/libraries/common/src/main/java/androidx/media3/common/SimpleBasePlayer.java @@ -28,6 +28,7 @@ import androidx.media3.common.text.CueGroup; import androidx.media3.common.util.Clock; import androidx.media3.common.util.HandlerWrapper; import androidx.media3.common.util.ListenerSet; +import androidx.media3.common.util.Size; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import com.google.common.base.Supplier; @@ -589,6 +590,12 @@ public abstract class SimpleBasePlayer extends BasePlayer { throw new IllegalStateException(); } + @Override + public final Size getVideoSurfaceSize() { + // TODO: implement. + throw new IllegalStateException(); + } + @Override public final CueGroup getCurrentCues() { // TODO: implement. diff --git a/libraries/common/src/main/java/androidx/media3/common/util/Size.java b/libraries/common/src/main/java/androidx/media3/common/util/Size.java new file mode 100644 index 0000000000..dddb834edd --- /dev/null +++ b/libraries/common/src/main/java/androidx/media3/common/util/Size.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2022 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 androidx.media3.common.util; + +import static androidx.media3.common.util.Assertions.checkArgument; + +import androidx.annotation.Nullable; +import androidx.media3.common.C; + +/** Immutable class for describing width and height dimensions in pixels. */ +@UnstableApi +public final class Size { + + /** A static instance to represent an unknown size value. */ + public static final Size UNKNOWN = + new Size(/* width= */ C.LENGTH_UNSET, /* height= */ C.LENGTH_UNSET); + + private final int width; + private final int height; + + /** + * Creates a new immutable Size instance. + * + * @param width The width of the size, in pixels, or {@link C#LENGTH_UNSET} if unknown. + * @param height The height of the size, in pixels, or {@link C#LENGTH_UNSET} if unknown. + * @throws IllegalArgumentException if an invalid {@code width} or {@code height} is specified. + */ + public Size(int width, int height) { + checkArgument( + (width == C.LENGTH_UNSET || width >= 0) && (height == C.LENGTH_UNSET || height >= 0)); + + this.width = width; + this.height = height; + } + + /** Returns the width of the size (in pixels), or {@link C#LENGTH_UNSET} if unknown. */ + public int getWidth() { + return width; + } + + /** Returns the height of the size (in pixels), or {@link C#LENGTH_UNSET} if unknown. */ + public int getHeight() { + return height; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof Size) { + Size other = (Size) obj; + return width == other.width && height == other.height; + } + return false; + } + + @Override + public String toString() { + return width + "x" + height; + } + + @Override + public int hashCode() { + // assuming most sizes are <2^16, doing a rotate will give us perfect hashing + return height ^ ((width << (Integer.SIZE / 2)) | (width >>> (Integer.SIZE / 2))); + } +} diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java index 64f1236bda..f3fc2357e1 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java @@ -82,6 +82,7 @@ import androidx.media3.common.util.ConditionVariable; import androidx.media3.common.util.HandlerWrapper; import androidx.media3.common.util.ListenerSet; import androidx.media3.common.util.Log; +import androidx.media3.common.util.Size; import androidx.media3.common.util.Util; import androidx.media3.exoplayer.PlayerMessage.Target; import androidx.media3.exoplayer.Renderer.MessageType; @@ -192,8 +193,7 @@ import java.util.concurrent.TimeoutException; @Nullable private TextureView textureView; private @C.VideoScalingMode int videoScalingMode; private @C.VideoChangeFrameRateStrategy int videoChangeFrameRateStrategy; - private int surfaceWidth; - private int surfaceHeight; + private Size surfaceSize; @Nullable private DecoderCounters videoDecoderCounters; @Nullable private DecoderCounters audioDecoderCounters; private int audioSessionId; @@ -382,6 +382,7 @@ import java.util.concurrent.TimeoutException; wifiLockManager.setEnabled(builder.wakeMode == C.WAKE_MODE_NETWORK); deviceInfo = createDeviceInfo(streamVolumeManager); videoSize = VideoSize.UNKNOWN; + surfaceSize = Size.UNKNOWN; trackSelector.setAudioAttributes(audioAttributes); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); @@ -1229,6 +1230,12 @@ import java.util.concurrent.TimeoutException; return videoSize; } + @Override + public Size getVideoSurfaceSize() { + verifyApplicationThread(); + return surfaceSize; + } + @Override public void clearVideoSurface() { verifyApplicationThread(); @@ -2545,9 +2552,8 @@ import java.util.concurrent.TimeoutException; } private void maybeNotifySurfaceSizeChanged(int width, int height) { - if (width != surfaceWidth || height != surfaceHeight) { - surfaceWidth = width; - surfaceHeight = height; + if (width != surfaceSize.getWidth() || height != surfaceSize.getHeight()) { + surfaceSize = new Size(width, height); listeners.sendEvent( EVENT_SURFACE_SIZE_CHANGED, listener -> listener.onSurfaceSizeChanged(width, height)); } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SimpleExoPlayer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SimpleExoPlayer.java index 93c452c766..01bc7fe708 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SimpleExoPlayer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SimpleExoPlayer.java @@ -44,6 +44,7 @@ import androidx.media3.common.VideoSize; import androidx.media3.common.text.CueGroup; import androidx.media3.common.util.Clock; import androidx.media3.common.util.ConditionVariable; +import androidx.media3.common.util.Size; import androidx.media3.common.util.UnstableApi; import androidx.media3.exoplayer.analytics.AnalyticsCollector; import androidx.media3.exoplayer.analytics.AnalyticsListener; @@ -528,6 +529,12 @@ public class SimpleExoPlayer extends BasePlayer return player.getVideoSize(); } + @Override + public Size getVideoSurfaceSize() { + blockUntilConstructorFinished(); + return player.getVideoSurfaceSize(); + } + @Override public void clearVideoSurface() { blockUntilConstructorFinished(); diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaController.java b/libraries/session/src/main/java/androidx/media3/session/MediaController.java index a729df8c50..37bf64666c 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaController.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaController.java @@ -54,6 +54,7 @@ import androidx.media3.common.VideoSize; import androidx.media3.common.text.CueGroup; import androidx.media3.common.util.Consumer; import androidx.media3.common.util.Log; +import androidx.media3.common.util.Size; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import com.google.common.util.concurrent.Futures; @@ -1498,6 +1499,13 @@ public class MediaController implements Player { return isConnected() ? impl.getVideoSize() : VideoSize.UNKNOWN; } + @UnstableApi + @Override + public Size getVideoSurfaceSize() { + verifyApplicationThread(); + return isConnected() ? impl.getVideoSurfaceSize() : Size.UNKNOWN; + } + @Override public void clearVideoSurface() { verifyApplicationThread(); @@ -1968,6 +1976,8 @@ public class MediaController implements Player { VideoSize getVideoSize(); + Size getVideoSurfaceSize(); + void clearVideoSurface(); void clearVideoSurface(@Nullable Surface surface); diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java index 389228847f..b810600900 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java @@ -75,6 +75,7 @@ import androidx.media3.common.util.BundleableUtil; import androidx.media3.common.util.Clock; import androidx.media3.common.util.ListenerSet; import androidx.media3.common.util.Log; +import androidx.media3.common.util.Size; import androidx.media3.common.util.Util; import androidx.media3.session.MediaController.MediaControllerImpl; import com.google.common.collect.ImmutableList; @@ -122,8 +123,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; @Nullable private Surface videoSurface; @Nullable private SurfaceHolder videoSurfaceHolder; @Nullable private TextureView videoTextureView; - private int surfaceWidth; - private int surfaceHeight; + private Size surfaceSize; @Nullable private IMediaSession iSession; private long lastReturnedContentPositionMs; private long lastSetPlayWhenReadyCalledTimeMs; @@ -136,6 +136,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; Looper applicationLooper) { // Initialize default values. playerInfo = PlayerInfo.DEFAULT; + surfaceSize = Size.UNKNOWN; sessionCommands = SessionCommands.EMPTY; playerCommandsFromSession = Commands.EMPTY; playerCommandsFromPlayer = Commands.EMPTY; @@ -1566,6 +1567,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; return playerInfo.videoSize; } + @Override + public Size getVideoSurfaceSize() { + return surfaceSize; + } + @Override public void clearVideoSurface() { if (!isPlayerCommandAvailable(Player.COMMAND_SET_VIDEO_SURFACE)) { @@ -2189,9 +2195,8 @@ import org.checkerframework.checker.nullness.qual.NonNull; } private void maybeNotifySurfaceSizeChanged(int width, int height) { - if (surfaceWidth != width || surfaceHeight != height) { - surfaceWidth = width; - surfaceHeight = height; + if (surfaceSize.getWidth() != width || surfaceSize.getHeight() != height) { + surfaceSize = new Size(width, height); listeners.sendEvent( /* eventFlag= */ Player.EVENT_SURFACE_SIZE_CHANGED, listener -> listener.onSurfaceSizeChanged(width, height)); diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java index 5d8c038971..3be371c530 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java @@ -67,6 +67,7 @@ import androidx.media3.common.text.CueGroup; import androidx.media3.common.util.Clock; import androidx.media3.common.util.ListenerSet; import androidx.media3.common.util.Log; +import androidx.media3.common.util.Size; import androidx.media3.common.util.Util; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Futures; @@ -961,6 +962,12 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; return VideoSize.UNKNOWN; } + @Override + public Size getVideoSurfaceSize() { + Log.w(TAG, "Session doesn't support getting VideoSurfaceSize"); + return Size.UNKNOWN; + } + @Override public void clearVideoSurface() { Log.w(TAG, "Session doesn't support clearing Surface"); diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MockPlayer.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MockPlayer.java index d05caf57e7..282c88ef3f 100644 --- a/libraries/test_session_current/src/main/java/androidx/media3/session/MockPlayer.java +++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MockPlayer.java @@ -18,6 +18,7 @@ package androidx.media3.session; import static androidx.media3.common.util.Assertions.checkNotNull; import static java.lang.annotation.ElementType.TYPE_USE; +import android.graphics.Rect; import android.os.Looper; import android.view.Surface; import android.view.SurfaceHolder; @@ -40,6 +41,7 @@ import androidx.media3.common.Tracks; import androidx.media3.common.VideoSize; import androidx.media3.common.text.CueGroup; import androidx.media3.common.util.ConditionVariable; +import androidx.media3.common.util.Size; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import com.google.common.collect.ImmutableList; @@ -230,6 +232,7 @@ public class MockPlayer implements Player { public @RepeatMode int repeatMode; public boolean shuffleModeEnabled; public VideoSize videoSize; + public Size surfaceSize; @Nullable public Surface surface; @Nullable public SurfaceHolder surfaceHolder; @Nullable public SurfaceView surfaceView; @@ -277,6 +280,7 @@ public class MockPlayer implements Player { currentMediaItemIndex = 0; repeatMode = Player.REPEAT_MODE_OFF; videoSize = VideoSize.UNKNOWN; + surfaceSize = Size.UNKNOWN; volume = 1.0f; cueGroup = CueGroup.EMPTY_TIME_ZERO; deviceInfo = DeviceInfo.UNKNOWN; @@ -1121,6 +1125,11 @@ public class MockPlayer implements Player { return videoSize; } + @Override + public Size getVideoSurfaceSize() { + return surfaceSize; + } + public void notifyVideoSizeChanged(VideoSize videoSize) { for (Listener listener : listeners) { listener.onVideoSizeChanged(videoSize); @@ -1136,47 +1145,70 @@ public class MockPlayer implements Player { public void clearVideoSurface(@Nullable Surface surface) { if (surface != null && surface == this.surface) { this.surface = null; + maybeUpdateSurfaceSize(/* width= */ 0, /* height= */ 0); } } @Override public void setVideoSurface(@Nullable Surface surface) { this.surface = surface; + int newSurfaceSize = surface == null ? 0 : C.LENGTH_UNSET; + maybeUpdateSurfaceSize(/* width= */ newSurfaceSize, /* height= */ newSurfaceSize); } @Override public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { this.surfaceHolder = surfaceHolder; + if (surfaceHolder == null || surfaceHolder.getSurfaceFrame() == null) { + maybeUpdateSurfaceSize(/* width= */ 0, /* height= */ 0); + } else { + Rect rect = surfaceHolder.getSurfaceFrame(); + maybeUpdateSurfaceSize(rect.width(), rect.height()); + } } @Override public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { if (surfaceHolder != null && surfaceHolder == this.surfaceHolder) { this.surfaceHolder = null; + maybeUpdateSurfaceSize(/* width= */ 0, /* height= */ 0); } } @Override public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) { this.surfaceView = surfaceView; + if (surfaceView == null + || surfaceView.getHolder() == null + || surfaceView.getHolder().getSurfaceFrame() == null) { + maybeUpdateSurfaceSize(/* width= */ 0, /* height= */ 0); + } else { + Rect rect = surfaceView.getHolder().getSurfaceFrame(); + maybeUpdateSurfaceSize(rect.width(), rect.height()); + } } @Override public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) { if (surfaceView != null && surfaceView == this.surfaceView) { this.surfaceView = null; + maybeUpdateSurfaceSize(/* width= */ 0, /* height= */ 0); } } @Override public void setVideoTextureView(@Nullable TextureView textureView) { this.textureView = textureView; + if (textureView != null) { + maybeUpdateSurfaceSize(textureView.getWidth(), textureView.getHeight()); + } } @Override public void clearVideoTextureView(@Nullable TextureView textureView) { if (textureView != null && textureView == this.textureView) { this.textureView = null; + maybeUpdateSurfaceSize(/* width= */ 0, /* height= */ 0); } } @@ -1278,6 +1310,12 @@ public class MockPlayer implements Player { } } + private void maybeUpdateSurfaceSize(int width, int height) { + if (width != surfaceSize.getWidth() || height != surfaceSize.getHeight()) { + surfaceSize = new Size(width, height); + } + } + private static ImmutableMap<@Method Integer, ConditionVariable> createMethodConditionVariables() { return new ImmutableMap.Builder<@Method Integer, ConditionVariable>() .put(METHOD_ADD_MEDIA_ITEM, new ConditionVariable()) diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/StubPlayer.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/StubPlayer.java index 97c7d3cf4a..77a9a609c9 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/StubPlayer.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/StubPlayer.java @@ -34,6 +34,7 @@ import androidx.media3.common.TrackSelectionParameters; import androidx.media3.common.Tracks; import androidx.media3.common.VideoSize; import androidx.media3.common.text.CueGroup; +import androidx.media3.common.util.Size; import androidx.media3.common.util.UnstableApi; import java.util.List; @@ -351,6 +352,11 @@ public class StubPlayer extends BasePlayer { throw new UnsupportedOperationException(); } + @Override + public Size getVideoSurfaceSize() { + throw new UnsupportedOperationException(); + } + @Override public CueGroup getCurrentCues() { throw new UnsupportedOperationException();