Commit a762f190 by bachinger Committed by Tofunmi Adigun-Hameed

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 f38cbad98558145b3b89f1bb57b25f3105ea36f9)
parent c4868228
......@@ -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"
......@@ -501,6 +501,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"
}
]
......
......@@ -981,7 +981,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.
*
* <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.
......@@ -3066,8 +3066,8 @@ public interface Player {
/**
* 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
* determined yet.
* <p>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)
*/
......
......@@ -630,6 +630,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
super.onDisabled();
} finally {
eventDispatcher.disabled(decoderCounters);
eventDispatcher.videoSizeChanged(VideoSize.UNKNOWN);
}
}
......
......@@ -163,6 +163,7 @@ import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.SystemClock;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
import com.google.android.exoplayer2.video.VideoSize;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
......@@ -332,6 +333,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<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. */
@Test
public void playShortDurationPeriods() throws Exception {
......
......@@ -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)
......
......@@ -27,6 +27,8 @@ import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.HandlerWrapper;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
import com.google.android.exoplayer2.video.VideoSize;
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}. */
......@@ -35,6 +37,7 @@ public class FakeVideoRenderer extends FakeRenderer {
private final HandlerWrapper handler;
private final VideoRendererEventListener eventListener;
private final DecoderCounters decoderCounters;
private final AtomicReference<VideoSize> videoSizeRef = new AtomicReference<>();
private @MonotonicNonNull Format format;
@Nullable private Object output;
private long streamOffsetUs;
......@@ -47,6 +50,7 @@ public class FakeVideoRenderer extends FakeRenderer {
this.handler = handler;
this.eventListener = eventListener;
decoderCounters = new DecoderCounters();
videoSizeRef.set(VideoSize.UNKNOWN);
}
@Override
......@@ -79,7 +83,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
......@@ -139,13 +148,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(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment