From b066a0912e7431d503ec770df846f8cd3c09be64 Mon Sep 17 00:00:00 2001 From: bachinger Date: Fri, 19 May 2023 17:48:32 +0100 Subject: [PATCH] Set video size to 0/0 when video render is disabled In terms of MCVR with a `VideoRendererEventListener`, the video size is set to 0/0 right after `onVideoDisabled()` is called and is set to the actual size as soon as the video size is known after 'onVideoEnabled()`. For ExoPlayer and in terms of the `Player` interface, `Player.getVideoSize()` returns a video size of 0/0 when `Player.getCurrentTracks()` does not support `C.TRACK_TYPE_VIDEO`. This is ensured by the masking behavior of `ExoPlayerImpl` that sets an empty track selection result when the playing period changes due to a seek or timeline removal. When transitioning playback from a video media item to the next, or when seeking within the same video media item, the renderer is not disabled. #minor-release PiperOrigin-RevId: 533479600 (cherry picked from commit 2a6f893fba763077cae85b0255d6bd05f0887709) --- RELEASENOTES.md | 12 ++++ demos/main/src/main/assets/media.exolist.json | 14 +++- .../java/androidx/media3/common/Player.java | 6 +- .../video/MediaCodecVideoRenderer.java | 1 + .../media3/exoplayer/ExoPlayerTest.java | 71 +++++++++++++++++++ .../DefaultAnalyticsCollectorTest.java | 50 ++++++++----- .../session/common/MediaSessionConstants.java | 3 + .../session/MediaControllerListenerTest.java | 52 +++++++++----- .../session/MediaSessionProviderService.java | 9 +++ .../media3/session/MediaTestUtils.java | 51 +++++++++++++ .../media3/test/utils/FakeVideoRenderer.java | 30 +++++--- 11 files changed, 250 insertions(+), 49 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 36b27199b6..ca36997083 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -19,6 +19,18 @@ * DRM: * Effect: * Muxers: +* IMA extension: + * Enable multi-period live DASH streams for DAI. Please note that the + current implementation does not yet support seeking in live streams + ([#10912](https://github.com/google/ExoPlayer/issues/10912)). +* ExoPlayer: + * Make `MediaCodecVideoRenderer` report a `VideoSize` with a width and + height of 0 when the renderer is disabled. + `Player.Listener.onVideoSizeChanged` is called accordingly when + `Player.getVideoSize()` changes. With this change, ExoPlayer's video + size with `MediaCodecVideoRenderer` has a width and height of 0 when + `Player.getCurrentTracks` does not support video, or the size of the + supported video track is not yet determined. * Session: * Add `androidx.media3.session.MediaButtonReceiver` to enable apps to implement playback resumption with media button events sent by, for diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index 3acef825d1..f37103c8fc 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -492,7 +492,7 @@ ] }, { - "name": "Audio -> Video -> Audio", + "name": "Audio -> Video (MKV) -> Video (MKV) -> Audio -> Video (MKV) -> Video (DASH) -> Audio", "playlist": [ { "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4" @@ -500,6 +500,18 @@ { "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv" }, + { + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv" + }, + { + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4" + }, + { + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv" + }, + { + "uri": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd" + }, { "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4" } 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 8d8e440175..0b3ee05817 100644 --- a/libraries/common/src/main/java/androidx/media3/common/Player.java +++ b/libraries/common/src/main/java/androidx/media3/common/Player.java @@ -990,7 +990,7 @@ public interface Player { default void onDeviceVolumeChanged(int volume, boolean muted) {} /** - * Called each time there's a change in the size of the video being rendered. + * Called each time when {@link Player#getVideoSize()} changes. * *

{@link #onEvents(Player, Events)} will also be called to report this event along with * other events that happen in the same {@link Looper} message queue iteration. @@ -3093,8 +3093,8 @@ public interface Player { /** * 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. + *

The video's width and height are {@code 0} if there is {@linkplain + * Tracks#isTypeSupported(int) no supported video track} or its size has not been determined yet. * * @see Listener#onVideoSizeChanged(VideoSize) */ diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java index 999d559e9d..3d848899ce 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java @@ -634,6 +634,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { super.onDisabled(); } finally { eventDispatcher.disabled(decoderCounters); + eventDispatcher.videoSizeChanged(VideoSize.UNKNOWN); } } diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java index dd74cd6b9e..b3ffa1fcee 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java @@ -116,6 +116,7 @@ import androidx.media3.common.Timeline; import androidx.media3.common.Timeline.Window; import androidx.media3.common.TrackGroup; import androidx.media3.common.Tracks; +import androidx.media3.common.VideoSize; import androidx.media3.common.util.Assertions; import androidx.media3.common.util.Clock; import androidx.media3.common.util.HandlerWrapper; @@ -342,6 +343,76 @@ public final class ExoPlayerTest { assertThat(renderer.isEnded).isTrue(); } + @Test + public void play_audioVideoAudioVideoTransition_videoSizeChangedCalledCorrectly() + throws Exception { + Timeline timeline = new FakeTimeline(/* windowCount= */ 1); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 0))); + Player.Listener mockPlayerListener = mock(Player.Listener.class); + player.addListener(mockPlayerListener); + AnalyticsListener mockAnalyticsListener = mock(AnalyticsListener.class); + player.addAnalyticsListener(mockAnalyticsListener); + player.setMediaSources( + ImmutableList.of( + new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT), + new FakeMediaSource( + timeline, ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT), + new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT), + new FakeMediaSource( + timeline, ExoPlayerTestRunner.VIDEO_FORMAT, ExoPlayerTestRunner.AUDIO_FORMAT))); + player.prepare(); + List videoSizesFromGetter = new ArrayList<>(); + player.addListener( + new Listener() { + @Override + public void onVideoSizeChanged(VideoSize videoSize) { + videoSizesFromGetter.add(player.getVideoSize()); + } + }); + + player.play(); + runUntilPlaybackState(player, Player.STATE_READY); + // Get the video size right after the first audio item was prepared. + videoSizesFromGetter.add(player.getVideoSize()); + runUntilPlaybackState(player, Player.STATE_ENDED); + videoSizesFromGetter.add(player.getVideoSize()); + player.release(); + ShadowLooper.runMainLooperToNextTask(); + + InOrder playerListenerOrder = inOrder(mockPlayerListener); + playerListenerOrder.verify(mockPlayerListener).onVideoSizeChanged(new VideoSize(1280, 720)); + playerListenerOrder.verify(mockPlayerListener).onVideoSizeChanged(VideoSize.UNKNOWN); + playerListenerOrder.verify(mockPlayerListener).onVideoSizeChanged(new VideoSize(1280, 720)); + playerListenerOrder.verify(mockPlayerListener).onPlaybackStateChanged(STATE_ENDED); + verify(mockPlayerListener, times(3)).onVideoSizeChanged(any()); + // Verify calls to analytics listener. + verify(mockAnalyticsListener, times(2)).onVideoEnabled(any(), any()); + verify(mockAnalyticsListener, times(2)).onVideoDisabled(any(), any()); + verify(mockAnalyticsListener).onAudioEnabled(any(), any()); + verify(mockAnalyticsListener).onAudioDisabled(any(), any()); + InOrder inOrder = Mockito.inOrder(mockAnalyticsListener); + inOrder.verify(mockAnalyticsListener).onAudioEnabled(any(), any()); + inOrder.verify(mockAnalyticsListener).onVideoEnabled(any(), any()); + inOrder.verify(mockAnalyticsListener).onVideoSizeChanged(any(), eq(new VideoSize(1280, 720))); + inOrder.verify(mockAnalyticsListener).onVideoDisabled(any(), any()); + inOrder.verify(mockAnalyticsListener).onVideoSizeChanged(any(), eq(VideoSize.UNKNOWN)); + inOrder.verify(mockAnalyticsListener).onVideoEnabled(any(), any()); + inOrder.verify(mockAnalyticsListener).onVideoSizeChanged(any(), eq(new VideoSize(1280, 720))); + inOrder.verify(mockAnalyticsListener).onVideoDisabled(any(), any()); + inOrder.verify(mockAnalyticsListener).onAudioDisabled(any(), any()); + verify(mockAnalyticsListener, times(3)).onVideoSizeChanged(any(), any()); + // Verify video sizes from getter. + assertThat(videoSizesFromGetter) + .containsExactly( + VideoSize.UNKNOWN, // When first item starts playing + new VideoSize(1280, 720), // When onVideoSizeChanged() called + VideoSize.UNKNOWN, // When onVideoSizeChanged() called + new VideoSize(1280, 720), // When onVideoSizeChanged() called + new VideoSize(1280, 720)) // In STATE_ENDED + .inOrder(); + } + /** Tests playback of periods with very short duration. */ @Test public void playShortDurationPeriods() throws Exception { diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/analytics/DefaultAnalyticsCollectorTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/analytics/DefaultAnalyticsCollectorTest.java index 84f6036683..eb5daa2d0a 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/analytics/DefaultAnalyticsCollectorTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/analytics/DefaultAnalyticsCollectorTest.java @@ -381,9 +381,7 @@ public final class DefaultAnalyticsCollectorTest { .containsExactly(period0, period1) .inOrder(); assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period1); - assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) - .containsExactly(period0, period1) - .inOrder(); + assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)) .containsExactly(period0, period1) .inOrder(); @@ -460,7 +458,11 @@ public final class DefaultAnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0); - assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) + .containsExactly( + period0, // First frame rendered of first video item + period1) // width=0, height=0 for audio only media source + .inOrder(); assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0); assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET)).containsExactly(period0); listener.assertNoMoreEvents(); @@ -554,7 +556,11 @@ public final class DefaultAnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(period0); - assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0); + assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) + .containsExactly( + period0, // First frame rendered of first video item + period1) // width=0, height=0 for audio only media source + .inOrder(); assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0); listener.assertNoMoreEvents(); } @@ -663,7 +669,10 @@ public final class DefaultAnalyticsCollectorTest { .containsExactly(period0, period1Seq2) .inOrder(); assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) - .containsExactly(period0, period1Seq1, period0, period1Seq2) + .containsExactly( + period0, // First frame rendered + period1Seq1, // Renderer disabled after seek + period0) // First frame rendered after seek .inOrder(); assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)) .containsExactly(period0, period1Seq1, period0, period1Seq2) @@ -766,7 +775,10 @@ public final class DefaultAnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(period0Seq0); assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq1); assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) - .containsExactly(period0Seq0, period0Seq1) + .containsExactly( + period0Seq0, // First frame rendered + period0Seq0, // Renderer disabled after timeline changed + period0Seq1) // First frame rendered of new source .inOrder(); assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)) .containsExactly(period0Seq0, period0Seq1) @@ -851,7 +863,7 @@ public final class DefaultAnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(period0Seq0); assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq0); assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) - .containsExactly(period0Seq0, period0Seq0); + .containsExactly(period0Seq0, period0Seq0, period0Seq0); assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)) .containsExactly(period0Seq0, period0Seq0); assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET)) @@ -938,7 +950,9 @@ public final class DefaultAnalyticsCollectorTest { .containsExactly(window0Period1Seq0, period1Seq0) .inOrder(); assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) - .containsExactly(window0Period1Seq0, window1Period0Seq1) + .containsExactly( + window0Period1Seq0, // First frame rendered + window0Period1Seq0) // Renderer disabled after timeline update .inOrder(); assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)) .containsExactly(window0Period1Seq0, window1Period0Seq1) @@ -1036,7 +1050,10 @@ public final class DefaultAnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(period0Seq0); assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq1); assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) - .containsExactly(period0Seq0, period1Seq1, period0Seq1) + .containsExactly( + period0Seq0, // First frame rendered + period0Seq1, // Renderer disabled after media item removal + period0Seq1) // First frame rendered after removal .inOrder(); assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)) .containsExactly(period0Seq0, period1Seq1, period0Seq1); @@ -1282,13 +1299,7 @@ public final class DefaultAnalyticsCollectorTest { .containsExactly(contentAfterPreroll, contentAfterMidroll, contentAfterPostroll) .inOrder(); assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) - .containsExactly( - prerollAd, - contentAfterPreroll, - midrollAd, - contentAfterMidroll, - postrollAd, - contentAfterPostroll) + .containsExactly(prerollAd) // First frame rendered .inOrder(); assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)) .containsExactly( @@ -1439,7 +1450,10 @@ public final class DefaultAnalyticsCollectorTest { assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(contentBeforeMidroll); assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(contentAfterMidroll); assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) - .containsExactly(contentBeforeMidroll, midrollAd, contentAfterMidroll) + .containsExactly( + contentBeforeMidroll, // First frame rendered + midrollAd, // Renderer disabled for seek + midrollAd) // First frame rendered after seek .inOrder(); assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)) .containsExactly(contentBeforeMidroll, midrollAd, contentAfterMidroll) diff --git a/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaSessionConstants.java b/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaSessionConstants.java index 480fe012ac..58e1434925 100644 --- a/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaSessionConstants.java +++ b/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaSessionConstants.java @@ -24,6 +24,9 @@ public class MediaSessionConstants { public static final String TEST_CONTROLLER_LISTENER_SESSION_REJECTS = "connection_sessionRejects"; public static final String TEST_IS_SESSION_COMMAND_AVAILABLE = "testIsSessionCommandAvailable"; public static final String TEST_COMMAND_GET_TRACKS = "testCommandGetTracksUnavailable"; + public static final String TEST_ON_VIDEO_SIZE_CHANGED = "onVideoSizeChanged"; + public static final String TEST_ON_TRACKS_CHANGED_VIDEO_TO_AUDIO_TRANSITION = + "onTracksChanged_videoToAudioTransition"; // Bundle keys public static final String KEY_AVAILABLE_SESSION_COMMANDS = "availableSessionCommands"; diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerTest.java index c09d550197..1cdf994b66 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerTest.java @@ -25,6 +25,7 @@ import static androidx.media3.test.session.common.MediaSessionConstants.KEY_COMM import static androidx.media3.test.session.common.MediaSessionConstants.KEY_CONTROLLER; import static androidx.media3.test.session.common.MediaSessionConstants.TEST_COMMAND_GET_TRACKS; import static androidx.media3.test.session.common.MediaSessionConstants.TEST_CONTROLLER_LISTENER_SESSION_REJECTS; +import static androidx.media3.test.session.common.MediaSessionConstants.TEST_ON_VIDEO_SIZE_CHANGED; import static androidx.media3.test.session.common.MediaSessionConstants.TEST_WITH_CUSTOM_COMMANDS; import static androidx.media3.test.session.common.TestUtils.LONG_TIMEOUT_MS; import static androidx.media3.test.session.common.TestUtils.NO_RESPONSE_TIMEOUT_MS; @@ -2545,40 +2546,53 @@ public class MediaControllerListenerTest { @Test public void onVideoSizeChanged() throws Exception { - VideoSize testVideoSize = - new VideoSize( - /* width= */ 100, - /* height= */ 42, - /* unappliedRotationDegrees= */ 90, - /* pixelWidthHeightRatio= */ 1.2f); - MediaController controller = controllerTestRule.createController(remoteSession.getToken()); - CountDownLatch latch = new CountDownLatch(2); - AtomicReference videoSizeFromParamRef = new AtomicReference<>(); - AtomicReference videoSizeFromGetterRef = new AtomicReference<>(); - AtomicReference eventsRef = new AtomicReference<>(); + VideoSize defaultVideoSize = MediaTestUtils.createDefaultVideoSize(); + RemoteMediaSession session = createRemoteMediaSession(TEST_ON_VIDEO_SIZE_CHANGED); + MediaController controller = controllerTestRule.createController(session.getToken()); + List videoSizeFromGetterList = new ArrayList<>(); + List videoSizeFromParamList = new ArrayList<>(); + List eventsList = new ArrayList<>(); + CountDownLatch latch = new CountDownLatch(6); Player.Listener listener = new Player.Listener() { @Override public void onVideoSizeChanged(VideoSize videoSize) { - videoSizeFromParamRef.set(videoSize); - videoSizeFromGetterRef.set(controller.getVideoSize()); + videoSizeFromParamList.add(videoSize); + videoSizeFromGetterList.add(controller.getVideoSize()); latch.countDown(); } @Override public void onEvents(Player player, Player.Events events) { - eventsRef.set(events); + eventsList.add(events); latch.countDown(); } }; - threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener)); + threadTestRule + .getHandler() + .postAndSync( + () -> { + controller.addListener(listener); + // Verify initial controller state. + assertThat(controller.getVideoSize()).isEqualTo(defaultVideoSize); + }); - remoteSession.getMockPlayer().notifyVideoSizeChanged(testVideoSize); + session.getMockPlayer().notifyVideoSizeChanged(VideoSize.UNKNOWN); + session.getMockPlayer().notifyVideoSizeChanged(defaultVideoSize); + session.getMockPlayer().notifyVideoSizeChanged(defaultVideoSize); + session.getMockPlayer().notifyVideoSizeChanged(VideoSize.UNKNOWN); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(videoSizeFromParamRef.get()).isEqualTo(testVideoSize); - assertThat(videoSizeFromGetterRef.get()).isEqualTo(testVideoSize); - assertThat(getEventsAsList(eventsRef.get())).containsExactly(Player.EVENT_VIDEO_SIZE_CHANGED); + assertThat(videoSizeFromParamList) + .containsExactly(VideoSize.UNKNOWN, defaultVideoSize, VideoSize.UNKNOWN) + .inOrder(); + assertThat(videoSizeFromGetterList) + .containsExactly(VideoSize.UNKNOWN, defaultVideoSize, VideoSize.UNKNOWN) + .inOrder(); + assertThat(eventsList).hasSize(3); + assertThat(getEventsAsList(eventsList.get(0))).containsExactly(Player.EVENT_VIDEO_SIZE_CHANGED); + assertThat(getEventsAsList(eventsList.get(1))).containsExactly(Player.EVENT_VIDEO_SIZE_CHANGED); + assertThat(getEventsAsList(eventsList.get(2))).containsExactly(Player.EVENT_VIDEO_SIZE_CHANGED); } @Test diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java index 65885ed84f..4fc87705c8 100644 --- a/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java +++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java @@ -62,6 +62,8 @@ import static androidx.media3.test.session.common.MediaSessionConstants.TEST_COM import static androidx.media3.test.session.common.MediaSessionConstants.TEST_CONTROLLER_LISTENER_SESSION_REJECTS; import static androidx.media3.test.session.common.MediaSessionConstants.TEST_GET_SESSION_ACTIVITY; import static androidx.media3.test.session.common.MediaSessionConstants.TEST_IS_SESSION_COMMAND_AVAILABLE; +import static androidx.media3.test.session.common.MediaSessionConstants.TEST_ON_TRACKS_CHANGED_VIDEO_TO_AUDIO_TRANSITION; +import static androidx.media3.test.session.common.MediaSessionConstants.TEST_ON_VIDEO_SIZE_CHANGED; import static androidx.media3.test.session.common.MediaSessionConstants.TEST_WITH_CUSTOM_COMMANDS; import android.app.PendingIntent; @@ -257,6 +259,13 @@ public class MediaSessionProviderService extends Service { }); break; } + case TEST_ON_TRACKS_CHANGED_VIDEO_TO_AUDIO_TRANSITION: + case TEST_ON_VIDEO_SIZE_CHANGED: + { + mockPlayer.videoSize = MediaTestUtils.createDefaultVideoSize(); + mockPlayer.currentTracks = MediaTestUtils.createDefaultVideoTracks(); + break; + } default: // fall out } diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MediaTestUtils.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MediaTestUtils.java index 990637680f..4619695176 100644 --- a/libraries/test_session_current/src/main/java/androidx/media3/session/MediaTestUtils.java +++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MediaTestUtils.java @@ -31,14 +31,21 @@ import android.support.v4.media.MediaDescriptionCompat; import android.support.v4.media.session.MediaSessionCompat; import androidx.annotation.Nullable; import androidx.media.MediaBrowserServiceCompat.BrowserRoot; +import androidx.media3.common.C; +import androidx.media3.common.Format; import androidx.media3.common.MediaItem; import androidx.media3.common.MediaMetadata; +import androidx.media3.common.MimeTypes; import androidx.media3.common.Timeline; +import androidx.media3.common.TrackGroup; +import androidx.media3.common.Tracks; +import androidx.media3.common.VideoSize; import androidx.media3.common.util.Log; import androidx.media3.session.MediaLibraryService.LibraryParams; import androidx.media3.session.MediaSession.ControllerInfo; import androidx.media3.test.session.common.TestUtils; import androidx.test.core.app.ApplicationProvider; +import com.google.common.collect.ImmutableList; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -50,6 +57,50 @@ public final class MediaTestUtils { private static final String TEST_IMAGE_PATH = "media/png/non-motion-photo-shortened.png"; + private static final VideoSize DEFAULT_VIDEO_SIZE = new VideoSize(640, 480); + private static final Format VIDEO_FORMAT = + new Format.Builder() + .setSampleMimeType(MimeTypes.VIDEO_H264) + .setAverageBitrate(2_400_000) + .setWidth(DEFAULT_VIDEO_SIZE.width) + .setHeight(DEFAULT_VIDEO_SIZE.height) + .build(); + private static final Format AUDIO_FORMAT = + new Format.Builder() + .setSampleMimeType(MimeTypes.AUDIO_AAC) + .setAverageBitrate(320_000) + .setChannelCount(2) + .setSampleRate(44100) + .build(); + + /** + * Tracks with {@linkplain C#TRACK_TYPE_VIDEO a single video} track and {@linkplain + * C#TRACK_TYPE_AUDIO a single audio} track for testing purpose. + */ + public static Tracks createDefaultVideoTracks() { + return new Tracks( + ImmutableList.of( + new Tracks.Group( + new TrackGroup(VIDEO_FORMAT), + /* adaptiveSupported= */ false, + new int[] {C.FORMAT_HANDLED}, + /* trackSelected= */ new boolean[] {true}), + new Tracks.Group( + new TrackGroup(AUDIO_FORMAT), + /* adaptiveSupported= */ false, + new int[] {C.FORMAT_HANDLED}, + /* trackSelected= */ new boolean[] {true}))); + } + + /** Returns a new {@link VideoSize} instance for testing purpose. */ + public static VideoSize createDefaultVideoSize() { + return new VideoSize( + DEFAULT_VIDEO_SIZE.width, + DEFAULT_VIDEO_SIZE.height, + DEFAULT_VIDEO_SIZE.unappliedRotationDegrees, + DEFAULT_VIDEO_SIZE.pixelWidthHeightRatio); + } + /** Create a media item with the mediaId for testing purpose. */ public static MediaItem createMediaItem(String mediaId) { MediaMetadata mediaMetadata = diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeVideoRenderer.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeVideoRenderer.java index 45de4ecad1..99cdeaaa38 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeVideoRenderer.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeVideoRenderer.java @@ -28,6 +28,8 @@ import androidx.media3.exoplayer.DecoderCounters; import androidx.media3.exoplayer.ExoPlaybackException; import androidx.media3.exoplayer.Renderer; import androidx.media3.exoplayer.video.VideoRendererEventListener; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** A {@link FakeRenderer} that supports {@link C#TRACK_TYPE_VIDEO}. */ @@ -37,6 +39,7 @@ public class FakeVideoRenderer extends FakeRenderer { private final HandlerWrapper handler; private final VideoRendererEventListener eventListener; private final DecoderCounters decoderCounters; + private final AtomicReference videoSizeRef = new AtomicReference<>(); private @MonotonicNonNull Format format; @Nullable private Object output; private long streamOffsetUs; @@ -49,6 +52,7 @@ public class FakeVideoRenderer extends FakeRenderer { this.handler = handler; this.eventListener = eventListener; decoderCounters = new DecoderCounters(); + videoSizeRef.set(VideoSize.UNKNOWN); } @Override @@ -81,7 +85,12 @@ public class FakeVideoRenderer extends FakeRenderer { @Override protected void onDisabled() { super.onDisabled(); - handler.post(() -> eventListener.onVideoDisabled(decoderCounters)); + videoSizeRef.set(VideoSize.UNKNOWN); + handler.post( + () -> { + eventListener.onVideoDisabled(decoderCounters); + eventListener.onVideoSizeChanged(VideoSize.UNKNOWN); + }); } @Override @@ -141,13 +150,18 @@ public class FakeVideoRenderer extends FakeRenderer { if (shouldProcess && !renderedFirstFrameAfterReset && output != null) { @MonotonicNonNull Format format = Assertions.checkNotNull(this.format); handler.post( - () -> - eventListener.onVideoSizeChanged( - new VideoSize( - format.width, - format.height, - format.rotationDegrees, - format.pixelWidthHeightRatio))); + () -> { + VideoSize videoSize = + new VideoSize( + format.width, + format.height, + format.rotationDegrees, + format.pixelWidthHeightRatio); + if (!Objects.equals(videoSize, videoSizeRef.get())) { + eventListener.onVideoSizeChanged(videoSize); + videoSizeRef.set(videoSize); + } + }); handler.post( () -> eventListener.onRenderedFirstFrame(