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
This commit is contained in:
parent
485517f34d
commit
2a6f893fba
@ -19,6 +19,18 @@
|
|||||||
* DRM:
|
* DRM:
|
||||||
* Effect:
|
* Effect:
|
||||||
* Muxers:
|
* 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:
|
* Session:
|
||||||
* Add `androidx.media3.session.MediaButtonReceiver` to enable apps to
|
* Add `androidx.media3.session.MediaButtonReceiver` to enable apps to
|
||||||
implement playback resumption with media button events sent by, for
|
implement playback resumption with media button events sent by, for
|
||||||
|
@ -492,7 +492,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Audio -> Video -> Audio",
|
"name": "Audio -> Video (MKV) -> Video (MKV) -> Audio -> Video (MKV) -> Video (DASH) -> Audio",
|
||||||
"playlist": [
|
"playlist": [
|
||||||
{
|
{
|
||||||
"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/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/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"
|
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4"
|
||||||
}
|
}
|
||||||
|
@ -990,7 +990,7 @@ public interface Player {
|
|||||||
default void onDeviceVolumeChanged(int volume, boolean muted) {}
|
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.
|
||||||
*
|
*
|
||||||
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
|
* <p>{@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.
|
* 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.
|
* Gets the size of the video.
|
||||||
*
|
*
|
||||||
* <p>The video's width and height are {@code 0} if there is no video or its size has not been
|
* <p>The video's width and height are {@code 0} if there is {@linkplain
|
||||||
* determined yet.
|
* Tracks#isTypeSupported(int) no supported video track} or its size has not been determined yet.
|
||||||
*
|
*
|
||||||
* @see Listener#onVideoSizeChanged(VideoSize)
|
* @see Listener#onVideoSizeChanged(VideoSize)
|
||||||
*/
|
*/
|
||||||
|
@ -634,6 +634,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
|||||||
super.onDisabled();
|
super.onDisabled();
|
||||||
} finally {
|
} finally {
|
||||||
eventDispatcher.disabled(decoderCounters);
|
eventDispatcher.disabled(decoderCounters);
|
||||||
|
eventDispatcher.videoSizeChanged(VideoSize.UNKNOWN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,6 +116,7 @@ import androidx.media3.common.Timeline;
|
|||||||
import androidx.media3.common.Timeline.Window;
|
import androidx.media3.common.Timeline.Window;
|
||||||
import androidx.media3.common.TrackGroup;
|
import androidx.media3.common.TrackGroup;
|
||||||
import androidx.media3.common.Tracks;
|
import androidx.media3.common.Tracks;
|
||||||
|
import androidx.media3.common.VideoSize;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
import androidx.media3.common.util.Clock;
|
import androidx.media3.common.util.Clock;
|
||||||
import androidx.media3.common.util.HandlerWrapper;
|
import androidx.media3.common.util.HandlerWrapper;
|
||||||
@ -342,6 +343,76 @@ public final class ExoPlayerTest {
|
|||||||
assertThat(renderer.isEnded).isTrue();
|
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<VideoSize> 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. */
|
/** Tests playback of periods with very short duration. */
|
||||||
@Test
|
@Test
|
||||||
public void playShortDurationPeriods() throws Exception {
|
public void playShortDurationPeriods() throws Exception {
|
||||||
|
@ -381,9 +381,7 @@ public final class DefaultAnalyticsCollectorTest {
|
|||||||
.containsExactly(period0, period1)
|
.containsExactly(period0, period1)
|
||||||
.inOrder();
|
.inOrder();
|
||||||
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period1);
|
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period1);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0);
|
||||||
.containsExactly(period0, period1)
|
|
||||||
.inOrder();
|
|
||||||
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
|
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
|
||||||
.containsExactly(period0, period1)
|
.containsExactly(period0, period1)
|
||||||
.inOrder();
|
.inOrder();
|
||||||
@ -460,7 +458,11 @@ public final class DefaultAnalyticsCollectorTest {
|
|||||||
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED)).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_DISABLED)).containsExactly(period0);
|
||||||
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).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_RENDERED_FIRST_FRAME)).containsExactly(period0);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET)).containsExactly(period0);
|
||||||
listener.assertNoMoreEvents();
|
listener.assertNoMoreEvents();
|
||||||
@ -554,7 +556,11 @@ public final class DefaultAnalyticsCollectorTest {
|
|||||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INITIALIZED)).containsExactly(period0);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED)).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_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);
|
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0);
|
||||||
listener.assertNoMoreEvents();
|
listener.assertNoMoreEvents();
|
||||||
}
|
}
|
||||||
@ -663,7 +669,10 @@ public final class DefaultAnalyticsCollectorTest {
|
|||||||
.containsExactly(period0, period1Seq2)
|
.containsExactly(period0, period1Seq2)
|
||||||
.inOrder();
|
.inOrder();
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
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();
|
.inOrder();
|
||||||
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
|
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
|
||||||
.containsExactly(period0, period1Seq1, period0, period1Seq2)
|
.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_VIDEO_DISABLED)).containsExactly(period0Seq0);
|
||||||
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq1);
|
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq1);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
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();
|
.inOrder();
|
||||||
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
|
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
|
||||||
.containsExactly(period0Seq0, period0Seq1)
|
.containsExactly(period0Seq0, period0Seq1)
|
||||||
@ -851,7 +863,7 @@ public final class DefaultAnalyticsCollectorTest {
|
|||||||
assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(period0Seq0);
|
assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(period0Seq0);
|
||||||
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq0);
|
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq0);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
||||||
.containsExactly(period0Seq0, period0Seq0);
|
.containsExactly(period0Seq0, period0Seq0, period0Seq0);
|
||||||
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
|
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
|
||||||
.containsExactly(period0Seq0, period0Seq0);
|
.containsExactly(period0Seq0, period0Seq0);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET))
|
assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET))
|
||||||
@ -938,7 +950,9 @@ public final class DefaultAnalyticsCollectorTest {
|
|||||||
.containsExactly(window0Period1Seq0, period1Seq0)
|
.containsExactly(window0Period1Seq0, period1Seq0)
|
||||||
.inOrder();
|
.inOrder();
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
||||||
.containsExactly(window0Period1Seq0, window1Period0Seq1)
|
.containsExactly(
|
||||||
|
window0Period1Seq0, // First frame rendered
|
||||||
|
window0Period1Seq0) // Renderer disabled after timeline update
|
||||||
.inOrder();
|
.inOrder();
|
||||||
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
|
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
|
||||||
.containsExactly(window0Period1Seq0, window1Period0Seq1)
|
.containsExactly(window0Period1Seq0, window1Period0Seq1)
|
||||||
@ -1036,7 +1050,10 @@ public final class DefaultAnalyticsCollectorTest {
|
|||||||
assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(period0Seq0);
|
assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(period0Seq0);
|
||||||
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq1);
|
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq1);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
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();
|
.inOrder();
|
||||||
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
|
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
|
||||||
.containsExactly(period0Seq0, period1Seq1, period0Seq1);
|
.containsExactly(period0Seq0, period1Seq1, period0Seq1);
|
||||||
@ -1282,13 +1299,7 @@ public final class DefaultAnalyticsCollectorTest {
|
|||||||
.containsExactly(contentAfterPreroll, contentAfterMidroll, contentAfterPostroll)
|
.containsExactly(contentAfterPreroll, contentAfterMidroll, contentAfterPostroll)
|
||||||
.inOrder();
|
.inOrder();
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
||||||
.containsExactly(
|
.containsExactly(prerollAd) // First frame rendered
|
||||||
prerollAd,
|
|
||||||
contentAfterPreroll,
|
|
||||||
midrollAd,
|
|
||||||
contentAfterMidroll,
|
|
||||||
postrollAd,
|
|
||||||
contentAfterPostroll)
|
|
||||||
.inOrder();
|
.inOrder();
|
||||||
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
|
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
@ -1439,7 +1450,10 @@ public final class DefaultAnalyticsCollectorTest {
|
|||||||
assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(contentBeforeMidroll);
|
assertThat(listener.getEvents(EVENT_VIDEO_DISABLED)).containsExactly(contentBeforeMidroll);
|
||||||
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(contentAfterMidroll);
|
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(contentAfterMidroll);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
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();
|
.inOrder();
|
||||||
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
|
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
|
||||||
.containsExactly(contentBeforeMidroll, midrollAd, contentAfterMidroll)
|
.containsExactly(contentBeforeMidroll, midrollAd, contentAfterMidroll)
|
||||||
|
@ -24,6 +24,9 @@ public class MediaSessionConstants {
|
|||||||
public static final String TEST_CONTROLLER_LISTENER_SESSION_REJECTS = "connection_sessionRejects";
|
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_IS_SESSION_COMMAND_AVAILABLE = "testIsSessionCommandAvailable";
|
||||||
public static final String TEST_COMMAND_GET_TRACKS = "testCommandGetTracksUnavailable";
|
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
|
// Bundle keys
|
||||||
public static final String KEY_AVAILABLE_SESSION_COMMANDS = "availableSessionCommands";
|
public static final String KEY_AVAILABLE_SESSION_COMMANDS = "availableSessionCommands";
|
||||||
|
@ -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.KEY_CONTROLLER;
|
||||||
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_COMMAND_GET_TRACKS;
|
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_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.MediaSessionConstants.TEST_WITH_CUSTOM_COMMANDS;
|
||||||
import static androidx.media3.test.session.common.TestUtils.LONG_TIMEOUT_MS;
|
import static androidx.media3.test.session.common.TestUtils.LONG_TIMEOUT_MS;
|
||||||
import static androidx.media3.test.session.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
|
import static androidx.media3.test.session.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
|
||||||
@ -2545,40 +2546,53 @@ public class MediaControllerListenerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onVideoSizeChanged() throws Exception {
|
public void onVideoSizeChanged() throws Exception {
|
||||||
VideoSize testVideoSize =
|
VideoSize defaultVideoSize = MediaTestUtils.createDefaultVideoSize();
|
||||||
new VideoSize(
|
RemoteMediaSession session = createRemoteMediaSession(TEST_ON_VIDEO_SIZE_CHANGED);
|
||||||
/* width= */ 100,
|
MediaController controller = controllerTestRule.createController(session.getToken());
|
||||||
/* height= */ 42,
|
List<VideoSize> videoSizeFromGetterList = new ArrayList<>();
|
||||||
/* unappliedRotationDegrees= */ 90,
|
List<VideoSize> videoSizeFromParamList = new ArrayList<>();
|
||||||
/* pixelWidthHeightRatio= */ 1.2f);
|
List<Player.Events> eventsList = new ArrayList<>();
|
||||||
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
|
CountDownLatch latch = new CountDownLatch(6);
|
||||||
CountDownLatch latch = new CountDownLatch(2);
|
|
||||||
AtomicReference<VideoSize> videoSizeFromParamRef = new AtomicReference<>();
|
|
||||||
AtomicReference<VideoSize> videoSizeFromGetterRef = new AtomicReference<>();
|
|
||||||
AtomicReference<Player.Events> eventsRef = new AtomicReference<>();
|
|
||||||
Player.Listener listener =
|
Player.Listener listener =
|
||||||
new Player.Listener() {
|
new Player.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void onVideoSizeChanged(VideoSize videoSize) {
|
public void onVideoSizeChanged(VideoSize videoSize) {
|
||||||
videoSizeFromParamRef.set(videoSize);
|
videoSizeFromParamList.add(videoSize);
|
||||||
videoSizeFromGetterRef.set(controller.getVideoSize());
|
videoSizeFromGetterList.add(controller.getVideoSize());
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEvents(Player player, Player.Events events) {
|
public void onEvents(Player player, Player.Events events) {
|
||||||
eventsRef.set(events);
|
eventsList.add(events);
|
||||||
latch.countDown();
|
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(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
assertThat(videoSizeFromParamRef.get()).isEqualTo(testVideoSize);
|
assertThat(videoSizeFromParamList)
|
||||||
assertThat(videoSizeFromGetterRef.get()).isEqualTo(testVideoSize);
|
.containsExactly(VideoSize.UNKNOWN, defaultVideoSize, VideoSize.UNKNOWN)
|
||||||
assertThat(getEventsAsList(eventsRef.get())).containsExactly(Player.EVENT_VIDEO_SIZE_CHANGED);
|
.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
|
@Test
|
||||||
|
@ -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_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_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_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 static androidx.media3.test.session.common.MediaSessionConstants.TEST_WITH_CUSTOM_COMMANDS;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
@ -257,6 +259,13 @@ public class MediaSessionProviderService extends Service {
|
|||||||
});
|
});
|
||||||
break;
|
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
|
default: // fall out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,14 +31,21 @@ import android.support.v4.media.MediaDescriptionCompat;
|
|||||||
import android.support.v4.media.session.MediaSessionCompat;
|
import android.support.v4.media.session.MediaSessionCompat;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
|
import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
|
||||||
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.MediaMetadata;
|
import androidx.media3.common.MediaMetadata;
|
||||||
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.Timeline;
|
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.common.util.Log;
|
||||||
import androidx.media3.session.MediaLibraryService.LibraryParams;
|
import androidx.media3.session.MediaLibraryService.LibraryParams;
|
||||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||||
import androidx.media3.test.session.common.TestUtils;
|
import androidx.media3.test.session.common.TestUtils;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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 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. */
|
/** Create a media item with the mediaId for testing purpose. */
|
||||||
public static MediaItem createMediaItem(String mediaId) {
|
public static MediaItem createMediaItem(String mediaId) {
|
||||||
MediaMetadata mediaMetadata =
|
MediaMetadata mediaMetadata =
|
||||||
|
@ -28,6 +28,8 @@ import androidx.media3.exoplayer.DecoderCounters;
|
|||||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
import androidx.media3.exoplayer.ExoPlaybackException;
|
||||||
import androidx.media3.exoplayer.Renderer;
|
import androidx.media3.exoplayer.Renderer;
|
||||||
import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
/** A {@link FakeRenderer} that supports {@link C#TRACK_TYPE_VIDEO}. */
|
/** 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 HandlerWrapper handler;
|
||||||
private final VideoRendererEventListener eventListener;
|
private final VideoRendererEventListener eventListener;
|
||||||
private final DecoderCounters decoderCounters;
|
private final DecoderCounters decoderCounters;
|
||||||
|
private final AtomicReference<VideoSize> videoSizeRef = new AtomicReference<>();
|
||||||
private @MonotonicNonNull Format format;
|
private @MonotonicNonNull Format format;
|
||||||
@Nullable private Object output;
|
@Nullable private Object output;
|
||||||
private long streamOffsetUs;
|
private long streamOffsetUs;
|
||||||
@ -49,6 +52,7 @@ public class FakeVideoRenderer extends FakeRenderer {
|
|||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.eventListener = eventListener;
|
this.eventListener = eventListener;
|
||||||
decoderCounters = new DecoderCounters();
|
decoderCounters = new DecoderCounters();
|
||||||
|
videoSizeRef.set(VideoSize.UNKNOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -81,7 +85,12 @@ public class FakeVideoRenderer extends FakeRenderer {
|
|||||||
@Override
|
@Override
|
||||||
protected void onDisabled() {
|
protected void onDisabled() {
|
||||||
super.onDisabled();
|
super.onDisabled();
|
||||||
handler.post(() -> eventListener.onVideoDisabled(decoderCounters));
|
videoSizeRef.set(VideoSize.UNKNOWN);
|
||||||
|
handler.post(
|
||||||
|
() -> {
|
||||||
|
eventListener.onVideoDisabled(decoderCounters);
|
||||||
|
eventListener.onVideoSizeChanged(VideoSize.UNKNOWN);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -141,13 +150,18 @@ public class FakeVideoRenderer extends FakeRenderer {
|
|||||||
if (shouldProcess && !renderedFirstFrameAfterReset && output != null) {
|
if (shouldProcess && !renderedFirstFrameAfterReset && output != null) {
|
||||||
@MonotonicNonNull Format format = Assertions.checkNotNull(this.format);
|
@MonotonicNonNull Format format = Assertions.checkNotNull(this.format);
|
||||||
handler.post(
|
handler.post(
|
||||||
() ->
|
() -> {
|
||||||
eventListener.onVideoSizeChanged(
|
VideoSize videoSize =
|
||||||
new VideoSize(
|
new VideoSize(
|
||||||
format.width,
|
format.width,
|
||||||
format.height,
|
format.height,
|
||||||
format.rotationDegrees,
|
format.rotationDegrees,
|
||||||
format.pixelWidthHeightRatio)));
|
format.pixelWidthHeightRatio);
|
||||||
|
if (!Objects.equals(videoSize, videoSizeRef.get())) {
|
||||||
|
eventListener.onVideoSizeChanged(videoSize);
|
||||||
|
videoSizeRef.set(videoSize);
|
||||||
|
}
|
||||||
|
});
|
||||||
handler.post(
|
handler.post(
|
||||||
() ->
|
() ->
|
||||||
eventListener.onRenderedFirstFrame(
|
eventListener.onRenderedFirstFrame(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user