Skip to content

Commit e800da7

Browse files
authored
[video_player_android] Modify to use handlesCropAndRotation to detect the SurfaceTexture Impeller backend (flutter#9107)
Changes SDK version check to a call to `SurfaceProducer.handlesCropAndRotation` to determine which Impeller backend is used for the `Texture` based video player and thus, what rotation correction to apply to correct the rotation of the video. Fixes flutter/flutter#157198. ## Pre-Review Checklist [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
1 parent 02a110b commit e800da7

File tree

9 files changed

+98
-44
lines changed

9 files changed

+98
-44
lines changed

packages/video_player/video_player_android/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 2.8.3
2+
3+
* Changes plugin to use `TextureRegistry.SurfaceProducer.handlesCropAndRotation` to detect
4+
whether or not the video player rotation needs to be corrected.
5+
16
## 2.8.2
27

38
* Fixes a [bug](https://github.com/flutter/flutter/issues/164689) that can cause video to

packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
import static androidx.media3.common.Player.REPEAT_MODE_OFF;
99

1010
import androidx.annotation.NonNull;
11+
import androidx.annotation.Nullable;
1112
import androidx.media3.common.AudioAttributes;
1213
import androidx.media3.common.C;
1314
import androidx.media3.common.MediaItem;
1415
import androidx.media3.common.PlaybackParameters;
1516
import androidx.media3.exoplayer.ExoPlayer;
17+
import io.flutter.view.TextureRegistry.SurfaceProducer;
1618

1719
/**
1820
* A class responsible for managing video playback using {@link ExoPlayer}.
@@ -24,6 +26,7 @@ public abstract class VideoPlayer {
2426
@NonNull private final MediaItem mediaItem;
2527
@NonNull private final VideoPlayerOptions options;
2628
@NonNull protected final VideoPlayerCallbacks videoPlayerEvents;
29+
@Nullable protected final SurfaceProducer surfaceProducer;
2730
@NonNull protected ExoPlayer exoPlayer;
2831

2932
/** A closure-compatible signature since {@link java.util.function.Supplier} is API level 24. */
@@ -41,11 +44,13 @@ public VideoPlayer(
4144
@NonNull VideoPlayerCallbacks events,
4245
@NonNull MediaItem mediaItem,
4346
@NonNull VideoPlayerOptions options,
47+
@Nullable SurfaceProducer surfaceProducer,
4448
@NonNull ExoPlayerProvider exoPlayerProvider) {
4549
this.videoPlayerEvents = events;
4650
this.mediaItem = mediaItem;
4751
this.options = options;
4852
this.exoPlayerProvider = exoPlayerProvider;
53+
this.surfaceProducer = surfaceProducer;
4954
this.exoPlayer = createVideoPlayer();
5055
}
5156

@@ -54,16 +59,15 @@ protected ExoPlayer createVideoPlayer() {
5459
ExoPlayer exoPlayer = exoPlayerProvider.get();
5560
exoPlayer.setMediaItem(mediaItem);
5661
exoPlayer.prepare();
57-
58-
exoPlayer.addListener(createExoPlayerEventListener(exoPlayer));
62+
exoPlayer.addListener(createExoPlayerEventListener(exoPlayer, surfaceProducer));
5963
setAudioAttributes(exoPlayer, options.mixWithOthers);
6064

6165
return exoPlayer;
6266
}
6367

6468
@NonNull
6569
protected abstract ExoPlayerEventListener createExoPlayerEventListener(
66-
@NonNull ExoPlayer exoPlayer);
70+
@NonNull ExoPlayer exoPlayer, @Nullable SurfaceProducer surfaceProducer);
6771

6872
void sendBufferingUpdate() {
6973
videoPlayerEvents.onBufferingUpdate(exoPlayer.getBufferedPosition());

packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewVideoPlayer.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import android.content.Context;
88
import androidx.annotation.NonNull;
9+
import androidx.annotation.Nullable;
910
import androidx.annotation.VisibleForTesting;
1011
import androidx.media3.common.MediaItem;
1112
import androidx.media3.exoplayer.ExoPlayer;
@@ -14,6 +15,7 @@
1415
import io.flutter.plugins.videoplayer.VideoPlayer;
1516
import io.flutter.plugins.videoplayer.VideoPlayerCallbacks;
1617
import io.flutter.plugins.videoplayer.VideoPlayerOptions;
18+
import io.flutter.view.TextureRegistry.SurfaceProducer;
1719

1820
/**
1921
* A subclass of {@link VideoPlayer} that adds functionality related to platform view as a way of
@@ -26,7 +28,7 @@ public PlatformViewVideoPlayer(
2628
@NonNull MediaItem mediaItem,
2729
@NonNull VideoPlayerOptions options,
2830
@NonNull ExoPlayerProvider exoPlayerProvider) {
29-
super(events, mediaItem, options, exoPlayerProvider);
31+
super(events, mediaItem, options, /* surfaceProducer */ null, exoPlayerProvider);
3032
}
3133

3234
/**
@@ -58,9 +60,10 @@ public static PlatformViewVideoPlayer create(
5860

5961
@NonNull
6062
@Override
61-
protected ExoPlayerEventListener createExoPlayerEventListener(@NonNull ExoPlayer exoPlayer) {
63+
protected ExoPlayerEventListener createExoPlayerEventListener(
64+
@NonNull ExoPlayer exoPlayer, @Nullable SurfaceProducer surfaceProducer) {
6265
// Platform view video player does not suspend and re-create the exoPlayer, hence initialized
63-
// is always false.
66+
// is always false. It also does not require a reference to the SurfaceProducer.
6467
return new PlatformViewExoPlayerEventListener(exoPlayer, videoPlayerEvents, false);
6568
}
6669
}

packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureExoPlayerEventListener.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,23 @@
1616
import java.util.Objects;
1717

1818
public final class TextureExoPlayerEventListener extends ExoPlayerEventListener {
19+
private boolean surfaceProducerHandlesCropAndRotation;
20+
1921
@VisibleForTesting
2022
public TextureExoPlayerEventListener(
21-
@NonNull ExoPlayer exoPlayer, @NonNull VideoPlayerCallbacks events) {
22-
this(exoPlayer, events, false);
23+
@NonNull ExoPlayer exoPlayer,
24+
@NonNull VideoPlayerCallbacks events,
25+
boolean surfaceProducerHandlesCropAndRotation) {
26+
this(exoPlayer, events, surfaceProducerHandlesCropAndRotation, false);
2327
}
2428

2529
public TextureExoPlayerEventListener(
26-
@NonNull ExoPlayer exoPlayer, @NonNull VideoPlayerCallbacks events, boolean initialized) {
30+
@NonNull ExoPlayer exoPlayer,
31+
@NonNull VideoPlayerCallbacks events,
32+
boolean surfaceProducerHandlesCropAndRotation,
33+
boolean initialized) {
2734
super(exoPlayer, events, initialized);
35+
this.surfaceProducerHandlesCropAndRotation = surfaceProducerHandlesCropAndRotation;
2836
}
2937

3038
@Override
@@ -51,10 +59,7 @@ protected void sendInitialized() {
5159
reportedRotationCorrection = RotationDegrees.ROTATE_0;
5260
rotationCorrection = 0;
5361
}
54-
}
55-
// TODO(camsim99): Replace this with a call to `handlesCropAndRotation` when it is
56-
// available in stable. https://github.com/flutter/flutter/issues/157198
57-
else if (Build.VERSION.SDK_INT < 29) {
62+
} else if (surfaceProducerHandlesCropAndRotation) {
5863
// When the SurfaceTexture backend for Impeller is used, the preview should already
5964
// be correctly rotated.
6065
rotationCorrection = 0;

packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureVideoPlayer.java

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import io.flutter.plugins.videoplayer.VideoPlayer;
1818
import io.flutter.plugins.videoplayer.VideoPlayerCallbacks;
1919
import io.flutter.plugins.videoplayer.VideoPlayerOptions;
20-
import io.flutter.view.TextureRegistry;
20+
import io.flutter.view.TextureRegistry.SurfaceProducer;
2121

2222
/**
2323
* A subclass of {@link VideoPlayer} that adds functionality related to texture view as a way of
@@ -26,9 +26,7 @@
2626
* <p>It manages the lifecycle of the texture and ensures that the video is properly displayed on
2727
* the texture.
2828
*/
29-
public final class TextureVideoPlayer extends VideoPlayer
30-
implements TextureRegistry.SurfaceProducer.Callback {
31-
@NonNull private final TextureRegistry.SurfaceProducer surfaceProducer;
29+
public final class TextureVideoPlayer extends VideoPlayer implements SurfaceProducer.Callback {
3230
@Nullable private ExoPlayerState savedStateDuring;
3331

3432
/**
@@ -45,7 +43,7 @@ public final class TextureVideoPlayer extends VideoPlayer
4543
public static TextureVideoPlayer create(
4644
@NonNull Context context,
4745
@NonNull VideoPlayerCallbacks events,
48-
@NonNull TextureRegistry.SurfaceProducer surfaceProducer,
46+
@NonNull SurfaceProducer surfaceProducer,
4947
@NonNull VideoAsset asset,
5048
@NonNull VideoPlayerOptions options) {
5149
return new TextureVideoPlayer(
@@ -64,23 +62,31 @@ public static TextureVideoPlayer create(
6462
@VisibleForTesting
6563
public TextureVideoPlayer(
6664
@NonNull VideoPlayerCallbacks events,
67-
@NonNull TextureRegistry.SurfaceProducer surfaceProducer,
65+
@NonNull SurfaceProducer surfaceProducer,
6866
@NonNull MediaItem mediaItem,
6967
@NonNull VideoPlayerOptions options,
7068
@NonNull ExoPlayerProvider exoPlayerProvider) {
71-
super(events, mediaItem, options, exoPlayerProvider);
69+
super(events, mediaItem, options, surfaceProducer, exoPlayerProvider);
7270

73-
this.surfaceProducer = surfaceProducer;
7471
surfaceProducer.setCallback(this);
7572

7673
this.exoPlayer.setVideoSurface(surfaceProducer.getSurface());
7774
}
7875

7976
@NonNull
8077
@Override
81-
protected ExoPlayerEventListener createExoPlayerEventListener(@NonNull ExoPlayer exoPlayer) {
78+
protected ExoPlayerEventListener createExoPlayerEventListener(
79+
@NonNull ExoPlayer exoPlayer, @Nullable SurfaceProducer surfaceProducer) {
80+
if (surfaceProducer == null) {
81+
throw new IllegalArgumentException(
82+
"surfaceProducer cannot be null to create an ExoPlayerEventListener for TextureVideoPlayer.");
83+
}
84+
boolean surfaceProducerHandlesCropAndRotation = surfaceProducer.handlesCropAndRotation();
8285
return new TextureExoPlayerEventListener(
83-
exoPlayer, videoPlayerEvents, playerHasBeenSuspended());
86+
exoPlayer,
87+
videoPlayerEvents,
88+
surfaceProducerHandlesCropAndRotation,
89+
playerHasBeenSuspended());
8490
}
8591

8692
@RestrictTo(RestrictTo.Scope.LIBRARY)

packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/TextureExoPlayerEventListenerTest.java

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import androidx.media3.common.VideoSize;
1313
import androidx.media3.exoplayer.ExoPlayer;
1414
import io.flutter.plugins.videoplayer.texture.TextureExoPlayerEventListener;
15-
import org.junit.Before;
1615
import org.junit.Rule;
1716
import org.junit.Test;
1817
import org.junit.runner.RunWith;
@@ -33,18 +32,28 @@
3332
public class TextureExoPlayerEventListenerTest {
3433
@Mock private ExoPlayer mockExoPlayer;
3534
@Mock private VideoPlayerCallbacks mockCallbacks;
36-
private TextureExoPlayerEventListener eventListener;
3735

3836
@Rule public MockitoRule initRule = MockitoJUnit.rule();
3937

40-
@Before
41-
public void setUp() {
42-
eventListener = new TextureExoPlayerEventListener(mockExoPlayer, mockCallbacks);
38+
@Test
39+
@Config(maxSdk = 21)
40+
public void onPlaybackStateChangedReadySendInitialized_belowAndroid21() {
41+
TextureExoPlayerEventListener eventListener =
42+
new TextureExoPlayerEventListener(mockExoPlayer, mockCallbacks, true);
43+
VideoSize size = new VideoSize(800, 400, 0, 0);
44+
when(mockExoPlayer.getVideoSize()).thenReturn(size);
45+
when(mockExoPlayer.getDuration()).thenReturn(10L);
46+
47+
eventListener.onPlaybackStateChanged(Player.STATE_READY);
48+
verify(mockCallbacks).onInitialized(800, 400, 10L, 0);
4349
}
4450

4551
@Test
46-
@Config(maxSdk = 28)
47-
public void onPlaybackStateChangedReadySendInitialized_belowAndroid29() {
52+
@Config(minSdk = 22)
53+
public void
54+
onPlaybackStateChangedReadySendInitialized_whenSurfaceProducerHandlesCropAndRotation() {
55+
TextureExoPlayerEventListener eventListener =
56+
new TextureExoPlayerEventListener(mockExoPlayer, mockCallbacks, true);
4857
VideoSize size = new VideoSize(800, 400, 0, 0);
4958
when(mockExoPlayer.getVideoSize()).thenReturn(size);
5059
when(mockExoPlayer.getDuration()).thenReturn(10L);
@@ -54,9 +63,11 @@ public void onPlaybackStateChangedReadySendInitialized_belowAndroid29() {
5463
}
5564

5665
@Test
57-
@Config(minSdk = 29)
66+
@Config(minSdk = 22)
5867
public void
59-
onPlaybackStateChangedReadySendInitializedWithRotationCorrectionAndWidthAndHeightSwap_aboveAndroid29() {
68+
onPlaybackStateChangedReadySendInitializedWithRotationCorrectionAndWidthAndHeightSwap_whenSurfaceProducerDoesNotHandleCropAndRotation() {
69+
TextureExoPlayerEventListener eventListener =
70+
new TextureExoPlayerEventListener(mockExoPlayer, mockCallbacks, false);
6071
VideoSize size = new VideoSize(800, 400, 0, 0);
6172
int rotationCorrection = 90;
6273
Format videoFormat = new Format.Builder().setRotationDegrees(rotationCorrection).build();
@@ -73,6 +84,8 @@ public void onPlaybackStateChangedReadySendInitialized_belowAndroid29() {
7384
@Config(maxSdk = 21)
7485
public void
7586
onPlaybackStateChangedReadyInPortraitMode90DegreesSwapWidthAndHeight_belowAndroid21() {
87+
TextureExoPlayerEventListener eventListener =
88+
new TextureExoPlayerEventListener(mockExoPlayer, mockCallbacks, true);
7689
VideoSize size = new VideoSize(800, 400, 90, 0);
7790
when(mockExoPlayer.getVideoSize()).thenReturn(size);
7891
when(mockExoPlayer.getDuration()).thenReturn(10L);
@@ -82,9 +95,11 @@ public void onPlaybackStateChangedReadySendInitialized_belowAndroid29() {
8295
}
8396

8497
@Test
85-
@Config(minSdk = 22, maxSdk = 28)
98+
@Config(minSdk = 22)
8699
public void
87-
onPlaybackStateChangedReadyInPortraitMode90DegreesDoesNotSwapWidthAndHeight_aboveAndroid21belowAndroid29() {
100+
onPlaybackStateChangedReadyInPortraitMode90DegreesDoesNotSwapWidthAndHeight_whenSurfaceProducerHandlesCropAndRotation() {
101+
TextureExoPlayerEventListener eventListener =
102+
new TextureExoPlayerEventListener(mockExoPlayer, mockCallbacks, true);
88103
VideoSize size = new VideoSize(800, 400, 90, 0);
89104

90105
when(mockExoPlayer.getVideoSize()).thenReturn(size);
@@ -95,9 +110,11 @@ public void onPlaybackStateChangedReadySendInitialized_belowAndroid29() {
95110
}
96111

97112
@Test
98-
@Config(minSdk = 29)
113+
@Config(minSdk = 22)
99114
public void
100-
onPlaybackStateChangedReadyInPortraitMode90DegreesSwapWidthAndHeight_aboveAndroid29() {
115+
onPlaybackStateChangedReadyInPortraitMode90DegreesSwapWidthAndHeight_whenSurfaceProducerDoesNotHandleCropAndRotation() {
116+
TextureExoPlayerEventListener eventListener =
117+
new TextureExoPlayerEventListener(mockExoPlayer, mockCallbacks, false);
101118
VideoSize size = new VideoSize(800, 400, 0, 0);
102119
int rotationCorrection = 90;
103120
Format videoFormat = new Format.Builder().setRotationDegrees(rotationCorrection).build();
@@ -114,6 +131,8 @@ public void onPlaybackStateChangedReadySendInitialized_belowAndroid29() {
114131
@Config(maxSdk = 21)
115132
public void
116133
onPlaybackStateChangedReadyInPortraitMode270DegreesSwapWidthAndHeight_belowAndroid21() {
134+
TextureExoPlayerEventListener eventListener =
135+
new TextureExoPlayerEventListener(mockExoPlayer, mockCallbacks, true);
117136
VideoSize size = new VideoSize(800, 400, 270, 0);
118137
when(mockExoPlayer.getVideoSize()).thenReturn(size);
119138
when(mockExoPlayer.getDuration()).thenReturn(10L);
@@ -123,9 +142,11 @@ public void onPlaybackStateChangedReadySendInitialized_belowAndroid29() {
123142
}
124143

125144
@Test
126-
@Config(minSdk = 22, maxSdk = 28)
145+
@Config(minSdk = 22)
127146
public void
128-
onPlaybackStateChangedReadyInPortraitMode270DegreesDoesNotSwapWidthAndHeight_aboveAndroid21belowAndroid29() {
147+
onPlaybackStateChangedReadyInPortraitMode270DegreesDoesNotSwapWidthAndHeight_whenSurfaceProducerHandlesCropAndRotation() {
148+
TextureExoPlayerEventListener eventListener =
149+
new TextureExoPlayerEventListener(mockExoPlayer, mockCallbacks, true);
129150
VideoSize size = new VideoSize(800, 400, 270, 0);
130151
when(mockExoPlayer.getVideoSize()).thenReturn(size);
131152
when(mockExoPlayer.getDuration()).thenReturn(10L);
@@ -135,9 +156,11 @@ public void onPlaybackStateChangedReadySendInitialized_belowAndroid29() {
135156
}
136157

137158
@Test
138-
@Config(minSdk = 29)
159+
@Config(minSdk = 22)
139160
public void
140-
onPlaybackStateChangedReadyInPortraitMode270DegreesSwapWidthAndHeight_aboveAndroid29() {
161+
onPlaybackStateChangedReadyInPortraitMode270DegreesDoesNotSwapWidthAndHeight_whenSurfaceProducerDoesNotHandleCropAndRotation() {
162+
TextureExoPlayerEventListener eventListener =
163+
new TextureExoPlayerEventListener(mockExoPlayer, mockCallbacks, false);
141164
VideoSize size = new VideoSize(800, 400, 0, 0);
142165
int rotationCorrection = 270;
143166
Format videoFormat = new Format.Builder().setRotationDegrees(rotationCorrection).build();
@@ -153,6 +176,8 @@ public void onPlaybackStateChangedReadySendInitialized_belowAndroid29() {
153176
@Test
154177
@Config(maxSdk = 21)
155178
public void onPlaybackStateChangedReadyFlipped180DegreesInformEventHandler_belowAndroid21() {
179+
TextureExoPlayerEventListener eventListener =
180+
new TextureExoPlayerEventListener(mockExoPlayer, mockCallbacks, true);
156181
VideoSize size = new VideoSize(800, 400, 180, 0);
157182
when(mockExoPlayer.getVideoSize()).thenReturn(size);
158183
when(mockExoPlayer.getDuration()).thenReturn(10L);

packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/TextureVideoPlayerTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public final class TextureVideoPlayerTest {
5555
public void setUp() {
5656
fakeVideoAsset = new FakeVideoAsset(FAKE_ASSET_URL);
5757
when(mockProducer.getSurface()).thenReturn(mock(Surface.class));
58+
when(mockProducer.handlesCropAndRotation()).thenReturn(true);
5859
}
5960

6061
private VideoPlayer createVideoPlayer() {
@@ -188,6 +189,7 @@ public void onSurfaceAvailableWithoutDestroyDoesNotRecreate() {
188189
// Initially create the video player, which creates the initial surface.
189190
VideoPlayer videoPlayer = createVideoPlayer();
190191
verify(mockProducer).getSurface();
192+
verify(mockProducer).handlesCropAndRotation();
191193

192194
// Capture the lifecycle events so we can simulate onSurfaceAvailable/Destroyed.
193195
verify(mockProducer).setCallback(callbackCaptor.capture());

0 commit comments

Comments
 (0)