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(